diff --git a/Makefile b/Makefile new file mode 100755 index 0000000..fcadcd9 --- /dev/null +++ b/Makefile @@ -0,0 +1,13 @@ +CFLAGS=-c -Wall -O0 +LIBS = + +all: image_to_c + +image_to_c: main.o + $(CC) main.o $(LIBS) -o image_to_c + +main.o: main.c + $(CC) $(CFLAGS) main.c + +clean: + rm -rf *.o image_to_c diff --git a/README b/README.md similarity index 100% rename from README rename to README.md diff --git a/badger.gif b/badger.gif new file mode 100644 index 0000000..7194ab3 Binary files /dev/null and b/badger.gif differ diff --git a/main.c b/main.c new file mode 100644 index 0000000..0fe6c9f --- /dev/null +++ b/main.c @@ -0,0 +1,771 @@ +// +// image_to_c - convert binary image files into c-compatible data tables +// +// Written by Larry Bank +// Copyright (c) 2020 BitBank Software, Inc. +// Change history +// 12/2/20 - Started the project +// +#include +#include +#include +#include + +#define TEMP_BUF_SIZE 4096 +#define DEFAULT_READ_SIZE 256 +#define MAX_TAGS 256 +#define TIFF_TAGSIZE 12 + +#define INTELSHORT(p) ((*p) + (*(p+1)<<8)) +#define INTELLONG(p) ((*p) + (*(p+1)<<8) + (*(p+2)<<16) + (*(p+3)<<24)) +#define MOTOSHORT(p) (((*(p))<<8) + (*(p+1))) +#define MOTOLONG(p) (((*p)<<24) + ((*(p+1))<<16) + ((*(p+2))<<8) + (*(p+3))) + +#ifdef _WIN32 +#define PILIO_SLASH_CHAR '\\' +#else +#define PILIO_SLASH_CHAR '/' +#endif + +typedef unsigned char BOOL; + +const char *szType[] = {"Unknown", "PNG","JFIF","Win BMP","OS/2 BMP","TIFF","GIF","Portable Pixmap","Targa","JEDMICS","CALS","PCX"}; +const char *szComp[] = {"Unknown", "Flate","JPEG","None","RLE","LZW","G3","G4","Packbits","Modified Huffman","Thunderscan RLE","JBIG (T.85)"}; +const char *szPhotometric[] = {"WhiteIsZero","BlackIsZero","RGB","Palette Color","Transparency Mask","CMYK","YCbCr","Unknown"}; +const char *szPlanar[] = {"Unknown","Chunky","Planar"}; + +enum +{ + FILETYPE_UNKNOWN = 0, + FILETYPE_PNG, + FILETYPE_JPEG, + FILETYPE_BMP, + FILETYPE_OS2BMP, + FILETYPE_TIFF, + FILETYPE_GIF, + FILETYPE_PPM, + FILETYPE_TARGA, + FILETYPE_JEDMICS, + FILETYPE_CALS, + FILETYPE_PCX +}; + +enum +{ + COMPTYPE_UNKNOWN = 0, + COMPTYPE_FLATE, + COMPTYPE_JPEG, + COMPTYPE_NONE, + COMPTYPE_RLE, + COMPTYPE_LZW, + COMPTYPE_G3, + COMPTYPE_G4, + COMPTYPE_PACKBITS, + COMPTYPE_HUFFMAN, + COMPTYPE_THUNDERSCAN, + COMPTYPE_JBIG +}; + +FILE * ihandle; +void MakeC(unsigned char *, int, int); +void GetLeafName(char *fname, char *leaf); +void FixName(char *name); + +unsigned short TIFFSHORT(unsigned char *p, BOOL bMotorola) +{ + unsigned short s; + + if (bMotorola) + s = *p * 0x100 + *(p+1); + else + s = *p + *(p+1)*0x100; + + return s; +} /* TIFFSHORT() */ + +uint32_t TIFFLONG(unsigned char *p, BOOL bMotorola) +{ + uint32_t l; + + if (bMotorola) + l = *p * 0x1000000 + *(p+1) * 0x10000 + *(p+2) * 0x100 + *(p+3); + else + l = *p + *(p+1) * 0x100 + *(p+2) * 0x10000 + *(p+3) * 0x1000000; + + return l; +} /* TIFFLONG() */ + +int TIFFVALUE(unsigned char *p, BOOL bMotorola) +{ + int i, iType; + + iType = TIFFSHORT(p+2, bMotorola); + /* If pointer to a list of items, must be a long */ + if (TIFFSHORT(p+4, bMotorola) > 1) + iType = 4; + switch (iType) + { + case 3: /* Short */ + i = TIFFSHORT(p+8, bMotorola); + break; + case 4: /* Long */ + case 7: // undefined (treat it as a long since it's usually a multibyte buffer) + i = TIFFLONG(p+8, bMotorola); + break; + case 6: // signed byte + i = (signed char)p[8]; + break; + case 2: /* ASCII */ + case 5: /* Unsigned Rational */ + case 10: /* Signed Rational */ + i = TIFFLONG(p+8, bMotorola); + break; + default: /* to suppress compiler warning */ + i = 0; + break; + } + return i; + +} /* TIFFVALUE() */ + +int ParseNumber(unsigned char *buf, int *iOff, int iLength) +{ + int i, iOffset; + + i = 0; + iOffset = *iOff; + + while (iOffset < iLength && buf[iOffset] >= '0' && buf[iOffset] <= '9') + { + i *= 10; + i += (int)(buf[iOffset++] - '0'); + } + *iOff = iOffset+1; /* Skip ending char */ + return i; + +} /* ParseNumber() */ + +// +// CountGIFFrames +// +// Given a pointer to a multipage GIF image +// This function will walk through the file and count the number +// of frames present +// +int CountGIFFrames(unsigned char *cBuf, int iFileSize) +{ + int iNumFrames; + size_t iOff; + int bDone = 0; + int bExt; + unsigned char c; + + iNumFrames = 0; + iOff = 10; + c = cBuf[iOff]; // get info bits + iOff += 3; /* Skip flags, background color & aspect ratio */ + if (c & 0x80) /* Deal with global color table */ + { + c &= 7; /* Get the number of colors defined */ + iOff += (2<= iFileSize) // problem + { + iNumFrames--; // possible corrupt data; stop + bDone = 1; + // *bTruncated = TRUE; + continue; + } + /* Start of image data */ + c = cBuf[iOff+9]; /* Get the flags byte */ + iOff += 10; /* Skip image position and size */ + if (c & 0x80) /* Local color table */ + { + c &= 7; + iOff += (2< iFileSize) // past end of file, stop + { + iNumFrames--; // don't count this frame + break; // last page is corrupted, don't use it + } + c = cBuf[iOff++]; /* Get length of next */ + } + /* End of image data, check for more frames... */ + if ((iOff > iFileSize) || cBuf[iOff] == 0x3b) + { + bDone = 1; /* End of file has been reached */ + } + else + { + iNumFrames++; + } + } /* while !bDone */ + return iNumFrames; + +} /* CountGIFFrames() */ + +void ImageInfo(FILE *iHandle, int iFileSize, char *szInfo) +{ + int i, j, k; + int iBytes; + int iFileType = FILETYPE_UNKNOWN; + int iCompression = COMPTYPE_UNKNOWN; + unsigned char cBuf[TEMP_BUF_SIZE]; // small buffer to load header info + int iBpp = 0; + int iWidth = 0; + int iHeight = 0; + int iOffset; + int iMarker; + int iPhotoMetric; + int iPlanar; + int iCount; + unsigned char ucSubSample; + BOOL bMotorola; + char szOptions[256]; + + szInfo[0] = 0; // start with null string + + // Detect the file type by its header + iBytes = fread(cBuf, 1, DEFAULT_READ_SIZE, iHandle); + if (iBytes != DEFAULT_READ_SIZE) + return; // too small + if (MOTOLONG(cBuf) == 0x89504e47) // PNG + iFileType = FILETYPE_PNG; + else if (cBuf[0] == 'B' && cBuf[1] == 'M') // BMP + { + if (cBuf[14] == 0x28) // Windows + iFileType = FILETYPE_BMP; + else + iFileType = FILETYPE_OS2BMP; + } + else if (cBuf[0] == 0x0a && cBuf[1] < 0x6 && cBuf[2] == 0x01) + { + iFileType = FILETYPE_PCX; + } + else if (INTELLONG(cBuf) == 0x80 && (cBuf[36] == 4 || cBuf[36] == 6)) + { + iFileType = FILETYPE_JEDMICS; + } + else if (INTELLONG(cBuf) == 0x64637273) + { + iFileType = FILETYPE_CALS; + } + else if ((MOTOLONG(cBuf) & 0xffffff00) == 0xffd8ff00) // JPEG + iFileType = FILETYPE_JPEG; + else if (MOTOLONG(cBuf) == 0x47494638 /*'GIF8'*/) // GIF + iFileType = FILETYPE_GIF; + else if ((cBuf[0] == 'I' && cBuf[1] == 'I') || (cBuf[0] == 'M' && cBuf[1] == 'M')) + iFileType = FILETYPE_TIFF; + else + { + i = MOTOLONG(cBuf) & 0xffff8080; + if (i == 0x50360000 || i == 0x50350000 || i == 0x50340000) // Portable bitmap/graymap/pixmap + iFileType = FILETYPE_PPM; + } + // Check for Truvision Targa + i = cBuf[1] & 0xfe; + j = cBuf[2]; + // make sure it is not a MPEG file (starts with 00 00 01 BA) + if (MOTOLONG(cBuf) != 0x1ba && MOTOLONG(cBuf) != 0x1b3 && i == 0 && (j == 1 || j == 2 || j == 3 || j == 9 || j == 10 || j == 11)) + iFileType = FILETYPE_TARGA; + + if (iFileType == FILETYPE_UNKNOWN) + { + return; + } + szOptions[0] = '\0'; // info specific to each file type + // Get info specific to each type of file + switch (iFileType) + { + case FILETYPE_PCX: + iWidth = 1 + INTELSHORT(&cBuf[8]) - INTELSHORT(&cBuf[4]); + iHeight = 1 + INTELSHORT(&cBuf[10]) - INTELSHORT(&cBuf[6]); + iCompression = COMPTYPE_PACKBITS; + iBpp = cBuf[3] * cBuf[65]; + break; + + case FILETYPE_PNG: + if (MOTOLONG(&cBuf[12]) == 0x49484452/*'IHDR'*/) + { + iWidth = MOTOLONG(&cBuf[16]); + iHeight = MOTOLONG(&cBuf[20]); + iCompression = COMPTYPE_FLATE; + i = cBuf[24]; // bits per pixel + j = cBuf[25]; // pixel type + switch (j) + { + case 0: // grayscale + case 3: // palette image + iBpp = i; + break; + case 2: // RGB triple + iBpp = i * 3; + break; + case 4: // grayscale + alpha channel + iBpp = i * 2; + break; + case 6: // RGB + alpha + iBpp = i * 4; + break; + } + if (cBuf[28] == 1) // interlace flag + strcpy(szOptions, ", Interlaced"); + else + strcpy(szOptions, ", Not interlaced"); + } + break; + case FILETYPE_TARGA: + iWidth = INTELSHORT(&cBuf[12]); + iHeight = INTELSHORT(&cBuf[14]); + iBpp = cBuf[16]; + if (cBuf[2] == 3 || cBuf[2] == 11) // monochrome + iBpp = 1; + if (cBuf[2] < 9) + iCompression = COMPTYPE_NONE; + else + iCompression = COMPTYPE_RLE; + break; + case FILETYPE_PPM: + if (cBuf[1] == '4') + iBpp = 1; + else if (cBuf[1] == '5') + iBpp = 8; + else if (cBuf[1] == '6') + iBpp = 24; + j = 2; + while ((cBuf[j] == 0xa || cBuf[j] == 0xd) && j>4),(ucSubSample & 0xf)); + } + break; + case FILETYPE_GIF: + iCompression = COMPTYPE_LZW; + iWidth = INTELSHORT(&cBuf[6]); + iHeight = INTELSHORT(&cBuf[8]); + iBpp = (cBuf[10] & 7) + 1; + if (cBuf[10] & 64) // interlace flag + strcpy(szOptions, ", Interlaced"); + else + strcpy(szOptions, ", Not interlaced"); + break; + case FILETYPE_TIFF: + bMotorola = (cBuf[0] == 'M'); // determine endianness of TIFF data + i = TIFFLONG(&cBuf[4], bMotorola); // get first IFD offset + fseek(iHandle, i, SEEK_SET);// read the entire tag directory + iBytes = fread(cBuf, 1, MAX_TAGS*TIFF_TAGSIZE, iHandle); + j = TIFFSHORT(cBuf, bMotorola); // get the tag count + iOffset = 2; // point to start of TIFF tag directory + // Some TIFF files don't specify everything, so set up some default values + iBpp = 1; + iPlanar = 1; + iCompression = COMPTYPE_NONE; + iPhotoMetric = 7; // if not specified, set to "unknown" + // Each TIFF tag is made up of 12 bytes + // byte 0-1: Tag value (short) + // byte 2-3: data type (short) + // byte 4-7: number of values (long) + // byte 8-11: value or offset to list of values + for (i=0; i 6) + iPhotoMetric = 7; // unknown + break; + case 284: // planar/chunky + iPlanar = TIFFVALUE(&cBuf[iOffset], bMotorola); + if (iPlanar < 1 || iPlanar > 2) // unknown value + iPlanar = 0; // unknown + break; + } // switch on tiff tag + iOffset += TIFF_TAGSIZE; + } // for each tag +// sprintf(szOptions, ", Photometric = %s, Planar config = %s", szPhotometric[iPhotoMetric], szPlanar[iPlanar]); +// break; + } // switch + sprintf(szInfo, "// %s, Compression=%s, Size: %d x %d, %d-Bpp\n", szType[iFileType], szComp[iCompression], iWidth, iHeight, iBpp); + if (iFileType == FILETYPE_GIF) // see how many frames it has + { + // slight hack - load the file into memory + uint8_t *pFile; + char szTemp[32]; + int iFrames; + pFile = malloc(iFileSize); + if (pFile != NULL) + { + fseek(iHandle, 0, SEEK_SET); + fread(pFile, 1, iFileSize, iHandle); + iFrames = CountGIFFrames(pFile, iFileSize); + free(pFile); + sprintf(szTemp, "// %d frames\n//\n", iFrames); + strcat(szInfo, szTemp); + } + } + else + { + strcat(szInfo, "//\n"); // simple end of comment + } +} /* ImageInfo() */ +// +// Main program entry point +// +int main(int argc, char *argv[]) +{ + int iSize, iData; + unsigned char *p; + char szLeaf[256]; + char szInfo[256]; + + if (argc != 2) + { + printf("image_to_c Copyright (c) 2020 BitBank Software, Inc.\n"); + printf("Written by Larry Bank\n\n"); + printf("Usage: image_to_c \n"); + printf("output is written to stdout\n"); + printf("example:\n\n"); + printf("image_to_c ./test.jpg > test.h\n"); + return 0; // no filename passed + } + ihandle = fopen(argv[1],"rb"); // open input file + if (ihandle == NULL) + { + fprintf(stderr, "Unable to open file: %s\n", argv[1]); + return -1; // bad filename passed + } + + fseek(ihandle, 0L, SEEK_END); // get the file size + iSize = (int)ftell(ihandle); + fseek(ihandle, 0, SEEK_SET); + ImageInfo(ihandle, iSize, szInfo); // get image info + p = (unsigned char *)malloc(0x10000); // allocate 64k to play with + GetLeafName(argv[1], szLeaf); + printf("//\n// %s\n//\n", szLeaf); // comment header with filename + if (szInfo[0]) + printf("%s", szInfo); + FixName(szLeaf); // remove unusable characters + printf("const uint8_t %s[] PROGMEM = {\n", szLeaf); // start of data array + fseek(ihandle, 0, SEEK_SET); + while (iSize) + { + iData = fread(p, 1, 0x10000, ihandle); // try to read 64k + MakeC(p, iData, iSize == iData); // create the output data + iSize -= iData; + } + free(p); + fclose(ihandle); + printf("};\n"); // final closing brace + return 0; +} /* main() */ +// +// Generate C hex characters from each byte of file data +// +void MakeC(unsigned char *p, int iLen, int bLast) +{ + int i, j, iCount; + char szTemp[256], szOut[256]; + + iCount = 0; + for (i=0; i>4; i++) // do lines of 16 bytes + { + strcpy(szOut, "\t"); + for (j=0; j<16; j++) + { + if (iCount == iLen-1 && bLast) // last one, skip the comma + sprintf(szTemp, "0x%02x", p[(i*16)+j]); + else + sprintf(szTemp, "0x%02x,", p[(i*16)+j]); + strcat(szOut, szTemp); + iCount++; + } + if (!bLast || iCount != iLen) + strcat(szOut, "\n"); + printf("%s",szOut); + } + p += (iLen & 0xfff0); // point to last section + if (iLen & 0xf) // any remaining characters? + { + strcpy(szOut, "\t"); + for (j=0; j<(iLen & 0xf); j++) + { + if (iCount == iLen-1 && bLast) + sprintf(szTemp, "0x%02x", p[j]); + else + sprintf(szTemp, "0x%02x,", p[j]); + strcat(szOut, szTemp); + iCount++; + } + if (!bLast) + strcat(szOut, "\n"); + printf("%s",szOut); + } +} /* MakeC() */ +// +// Make sure the name can be used in C/C++ as a variable +// replace invalid characters and make sure it starts with a letter +// +void FixName(char *name) +{ + char c, *d, *s, szTemp[256]; + int i, iLen; + + iLen = strlen(name); + d = szTemp; + s = name; + if (s[0] >= '0' && s[0] <= '9') // starts with a digit + *d++ = '_'; // Insert an underscore + for (i=0; i= '!' && c < '0') || (c > 'Z' && c < 'a')) + c = '_'; // convert all to an underscore + *d++ = c; + } + *d++ = 0; + strcpy(name, szTemp); +} /* FixName() */ +// +// Trim off the leaf name from a fully +// formed file pathname +// +void GetLeafName(char *fname, char *leaf) +{ + int i, iLen; + + iLen = strlen(fname); + for (i=iLen-1; i>=0; i--) + { + if (fname[i] == '\\' || fname[i] == '/') // Windows or Linux + break; + } + strcpy(leaf, &fname[i+1]); + // remove the filename extension + iLen = strlen(leaf); + for (i=iLen-1; i>=0; i--) + { + if (leaf[i] == '.') + { + leaf[i] = 0; + break; + } + } +} /* GetLeafName() */ + diff --git a/screenshot.png b/screenshot.png new file mode 100644 index 0000000..9b37f77 Binary files /dev/null and b/screenshot.png differ