/**************************************************************************** * apps/graphics/traveler/tools/libwld/wld_loadgif.c * * Copyright (C) 2016 Gregory Nutt. All rights reserved. * Author: Gregory Nutt * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * 3. Neither the name NuttX nor the names of its contributors may be * used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * ****************************************************************************/ /************************************************************************* * Included files *************************************************************************/ #include #include #include #include #include "trv_types.h" #include "wld_mem.h" #include "wld_bitmaps.h" #include "wld_graphicfile.h" #include "wld_utils.h" /************************************************************************* * Pre-processor Definitions *************************************************************************/ #define NEXTBYTE (*ptr++) #define IMAGESEP 0x2c #define GRAPHIC_EXT 0xf9 #define PLAINTEXT_EXT 0x01 #define APPLICATION_EXT 0xff #define COMMENT_EXT 0xfe #define START_EXTENSION 0x21 #define INTERLACEMASK 0x40 #define COLORMAPMASK 0x80 /************************************************************************* * Private Data *************************************************************************/ static int BitOffset = 0; /* Bit Offset of next code */ static int XC = 0, YC = 0; /* Output X and Y coords of current pixel */ static int Pass = 0; /* Used by output routine if interlaced pic */ static int OutCount = 0; /* Decompressor output 'stack count' */ static int RWidth, RHeight; /* screen dimensions */ static int Width, Height; /* image dimensions */ static int LeftOfs, TopOfs; /* image offset */ static int BitsPerPixel; /* Bits per pixel, read from GIF header */ static int BytesPerScanline; /* bytes per scanline in output rwld_er */ static int ColorMapSize; /* number of colors */ static int Background; /* background color */ static int CodeSize; /* Code size, read from GIF header */ static int InitCodeSize; /* Starting code size, used during Clear */ static int Code; /* Value returned by ReadCode */ static int MaxCode; /* limiting value for current code size */ static int ClearCode; /* GIF clear code */ static int EOFCode; /* GIF end-of-information code */ static int CurCode, OldCode, InCode; /* Decompressor variables */ static int FirstFree; /* First free code, generated per GIF spec */ static int FreeCode; /* Decompressor, next free slot in hash table */ static int FinChar; /* Decompressor variable */ static int BitMask; /* AND mask for data size */ static int ReadMask; /* Code AND mask for current code size */ static bool Interlace, HasColormap; static bool Verbose = true; static uint8_t *Image; /* The result array */ static uint8_t *RawGIF; /* The heap array to hold it, raw */ static uint8_t *Rwld_er; /* The rwld_er data stream, unblocked */ /* The hash table used by the decompressor */ static int Prefix[4096]; static int Suffix[4096]; /* An output array used by the decompressor */ static int OutCode[1025]; /* The color map, read from the GIF header */ static uint8_t Red[256], Green[256], Blue[256], used[256]; static int numused; static char *id87 = "GIF87a"; static char *id89 = "GIF89a"; /************************************************************************* * Private Functions ************************************************************************/ /************************************************************************* * Name: ReadCode * Description: * Fetch the next code from the data stream. The codes can be * any length from 3 to 12 bits, packed into 8-bit bytes, so we have to * maintain our location in the Rwld_er array as a BIT Offset. We compute * the byte Offset into the rwld_er array by dividing this by 8, pick up * three bytes, compute the bit Offset into our 24-bit chunk, shift to * bring the desired code to the bottom, then mask it off and return it. ************************************************************************/ static int ReadCode(void) { int RawCode; int ByteOffset; ByteOffset = BitOffset / 8; RawCode = Rwld_er[ByteOffset] + (0x100 * Rwld_er[ByteOffset + 1]); if (CodeSize >= 8) RawCode += (0x10000 * Rwld_er[ByteOffset + 2]); RawCode >>= (BitOffset % 8); BitOffset += CodeSize; return (RawCode & ReadMask); } static void AddToPixel(uint8_t Index) { if (YC < Height) *(Image + YC * BytesPerScanline + XC) = Index; if (!used[Index]) { used[Index] = 1; numused++; } /* Update the X-coordinate, and if it overflows, update the Y-coordinate */ if (++XC == Width) { /* If a non-interlaced picture, just increment YC to the next scan line. * If it's interlaced, deal with the interlace as described in the GIF * spec. Put the decoded scan line out to the screen if we haven't gone * pwld_ the bottom of it */ XC = 0; if (!Interlace) { YC++; } else { switch (Pass) { case 0: YC += 8; if (YC >= Height) { Pass++; YC = 4; } break; case 1: YC += 8; if (YC >= Height) { Pass++; YC = 2; } break; case 2: YC += 4; if (YC >= Height) { Pass++; YC = 1; } break; case 3: YC += 2; break; default: break; } } } } /************************************************************************* * Public Functions ************************************************************************/ /************************************************************************* * Name: wld_LoadGIF * Description: ************************************************************************/ graphic_file_t *wld_LoadGIF(FILE * fp, char *fname) { graphic_file_t *gfile; int filesize; register unsigned char ch; register unsigned char ch1; register uint8_t *ptr; register uint8_t *ptr1; register int i; short transparency = -1; BitOffset = 0; XC = YC = 0; Pass = 0; OutCount = 0; /* Find the size of the file */ fseek(fp, 0L, 2); filesize = ftell(fp); fseek(fp, 0L, 0); if (!(ptr = RawGIF = (uint8_t *) malloc(filesize))) { wld_fatal_error("not enough memory to read gif file"); } if (!(Rwld_er = (uint8_t *) malloc(filesize))) { free(ptr); wld_fatal_error("not enough memory to read gif file"); } if (fread(ptr, filesize, 1, fp) != 1) wld_fatal_error("GIF data read failed"); if (strncmp((char *)ptr, id87, 6)) { if (strncmp((char *)ptr, id89, 6)) { wld_fatal_error("not a GIF file"); } } ptr += 6; /* Get variables from the GIF screen descriptor */ ch = NEXTBYTE; RWidth = ch + 0x100 * NEXTBYTE; /* screen dimensions... not used. */ ch = NEXTBYTE; RHeight = ch + 0x100 * NEXTBYTE; if (Verbose) { fprintf(stderr, "screen dims: %dx%d.\n", RWidth, RHeight); } ch = NEXTBYTE; HasColormap = ((ch & COLORMAPMASK) ? true : false); BitsPerPixel = (ch & 7) + 1; ColorMapSize = 1 << BitsPerPixel; BitMask = ColorMapSize - 1; Background = NEXTBYTE; /* background color... not used. */ if (NEXTBYTE) /* supposed to be NULL */ { wld_fatal_error("corrupt GIF file (bad screen descriptor)"); } /* Read in global colormap. */ if (HasColormap) { if (Verbose) { fprintf(stderr, "%s is %dx%d, %d bits per pixel, (%d colors).\n", fname, RWidth, RHeight, BitsPerPixel, ColorMapSize); } for (i = 0; i < ColorMapSize; i++) { Red[i] = NEXTBYTE; Green[i] = NEXTBYTE; Blue[i] = NEXTBYTE; used[i] = 0; } numused = 0; } /* look for image separator */ for (ch = NEXTBYTE; ch != IMAGESEP; ch = NEXTBYTE) { i = ch; fprintf(stderr, "EXTENSION CHARACTER: %x\n", i); if (ch != START_EXTENSION) wld_fatal_error("corrupt GIF89a file"); /* Handle image extensions */ switch (ch = NEXTBYTE) { case GRAPHIC_EXT: ch = NEXTBYTE; if (ptr[0] & 0x1) { transparency = ptr[3]; /* transparent color index */ fprintf(stderr, "transparency index: %i\n", transparency); } ptr += ch; break; case PLAINTEXT_EXT: break; case APPLICATION_EXT: break; case COMMENT_EXT: break; default: wld_fatal_error("invalid GIF89 extension"); } while ((ch = NEXTBYTE)) { ptr += ch; } } /* Now read in values from the image descriptor */ ch = NEXTBYTE; LeftOfs = ch + 0x100 * NEXTBYTE; ch = NEXTBYTE; TopOfs = ch + 0x100 * NEXTBYTE; ch = NEXTBYTE; Width = ch + 0x100 * NEXTBYTE; ch = NEXTBYTE; Height = ch + 0x100 * NEXTBYTE; Interlace = ((NEXTBYTE & INTERLACEMASK) ? true : false); if (Verbose) fprintf(stderr, "Reading a %d by %d %sinterlaced image...", Width, Height, (Interlace) ? "" : "non-"); gfile = wld_new_graphicfile(); gfile->palette = (color_rgb_t *) wld_malloc(sizeof(color_rgb_t) * ColorMapSize); for (i = 0; i < ColorMapSize; i++) { gfile->palette[i].red = Red[i]; gfile->palette[i].green = Green[i]; gfile->palette[i].blue = Blue[i]; } gfile->bitmap = (uint8_t *) wld_malloc(Width * Height); gfile->type = GFILE_PALETTED; gfile->width = Width; gfile->height = Height; gfile->transparent_entry = transparency; /* Note that I ignore the possible existence of a local color map. I'm told * there aren't many files around that use them, and the spec says it's * defined for future use. This could lead to an error reading some files. */ /* Start reading the rwld_er data. First we get the intial code size and * compute decompressor constant values, based on this code size. */ CodeSize = NEXTBYTE; ClearCode = (1 << CodeSize); EOFCode = ClearCode + 1; FreeCode = FirstFree = ClearCode + 2; /* The GIF spec has it that the code size is the code size used to compute * the above values is the code size given in the file, but the code size * used in compression/decompression is the code size given in the file plus * one. (thus the ++). */ CodeSize++; InitCodeSize = CodeSize; MaxCode = (1 << CodeSize); ReadMask = MaxCode - 1; /* Read the rwld_er data. Here we just transpose it from the GIF array to * the Rwld_er array, turning it from a series of blocks into one long data * stream, which makes life much easier for ReadCode(). */ ptr1 = Rwld_er; do { ch = ch1 = NEXTBYTE; while (ch--) *ptr1++ = NEXTBYTE; if ((ptr1 - Rwld_er) > filesize) wld_fatal_error("corrupt GIF file (unblock)"); } while (ch1); free(RawGIF); /* We're done with the raw data now... */ if (Verbose) { fprintf(stderr, "done.\n"); fprintf(stderr, "Decompressing..."); } Image = gfile->bitmap; BytesPerScanline = Width; /* Decompress the file, continuing until you see the GIF EOF code. One * obvious enhancement is to add checking for corrupt files here. */ Code = ReadCode(); while (Code != EOFCode) { /* Clear code sets everything back to its initial value, then reads the * immediately subsequent code as uncompressed data. */ if (Code == ClearCode) { CodeSize = InitCodeSize; MaxCode = (1 << CodeSize); ReadMask = MaxCode - 1; FreeCode = FirstFree; CurCode = OldCode = Code = ReadCode(); FinChar = CurCode & BitMask; AddToPixel(FinChar); } else { /* If not a clear code, then must be data: save same as CurCode and * InCode */ CurCode = InCode = Code; /* If greater or equal to FreeCode, not in the hash table yet; repeat * the lwld_ character decoded */ if (CurCode >= FreeCode) { CurCode = OldCode; OutCode[OutCount++] = FinChar; } /* Unless this code is raw data, pursue the chain pointed to by * CurCode through the hash table to its end; each code in the chain * puts its associated output code on the output queue. */ while (CurCode > BitMask) { if (OutCount > 1024) { fprintf(stderr, "\nCorrupt GIF file (OutCount)!\n"); exit(-1); } OutCode[OutCount++] = Suffix[CurCode]; CurCode = Prefix[CurCode]; } /* The lwld_ code in the chain is treated as raw data. */ FinChar = CurCode & BitMask; OutCode[OutCount++] = FinChar; /* Now we put the data out to the Output routine. It's been stacked * LIFO, so deal with it that way... */ for (i = OutCount - 1; i >= 0; i--) AddToPixel(OutCode[i]); OutCount = 0; /* Build the hash table on-the-fly. No table is stored in the file */ Prefix[FreeCode] = OldCode; Suffix[FreeCode] = FinChar; OldCode = InCode; /* Point to the next slot in the table. If we exceed the current * MaxCode value, increment the code size unless it's already 12. If * it is, do nothing: the next code decompressed better be CLEAR */ FreeCode++; if (FreeCode >= MaxCode) { if (CodeSize < 12) { CodeSize++; MaxCode *= 2; ReadMask = (1 << CodeSize) - 1; } } } Code = ReadCode(); } free(Rwld_er); if (Verbose) { fprintf(stderr, "done.\n"); } else { fprintf(stderr, "(of which %d are used)\n", numused); } return gfile; }