/** * @file imageio.c * @brief Implements ReadImage and WriteImage functions * @author Pascal Getreuer * * Two high-level functions are provided, \c ReadImage and \c WriteImage, for * reading and writing image BMP, JPEG, PNG, and TIFF files. The desired * format of the image data can be specified to \c ReadImage for how to return * the data (and similarly to \c WriteImage for how it should interpret the * data). Formatting options allow specifying the datatype of the components, * conversion to grayscale, channel ordering, interleaved vs. planar, and * row-major vs. column-major. * * \c ReadImage automatically detects the format of the image being read so * that the format does not need to be supplied explicitly. \c WriteImage * infers the file format from the file extension. * * Also included is a function \c IdentifyImageType to guess the file type (BMP, * JPEG, PNG, TIFF, and a few other formats) from the file header's magic * numbers without reading the image. * * Support for BMP reading and writing is native: BMP reading supports 1-, 2-, * 4-, 8-, 16-, 32-bit uncompressed, RLE, and bitfield images; BMP writing is * limited to 24-bit uncompressed. The implementation calls libjpeg, libpng, * and libtiff to handle JPEG, PNG, and TIFF images. * * * Copyright (c) 2010-2012, Pascal Getreuer * All rights reserved. * * This program is free software: you can use, modify and/or * redistribute it under the terms of the simplified BSD License. You * should have received a copy of this license along this program. If * not, see . */ #include #include #include "imageio.h" #ifdef USE_LIBPNG #include #include #if PNG_LIBPNG_VER < 10400 /* For compatibility with older libpng */ #define png_set_expand_gray_1_2_4_to_8 png_set_gray_1_2_4_to_8 #endif #endif #ifdef USE_LIBTIFF #include #endif #ifdef USE_LIBJPEG #include #include #endif /** @brief Buffer size to use for BMP file I/O */ #define FILE_BUFFER_CAPACITY (1024*4) #define ROUNDCLAMPF(x) ((x < 0.0f) ? 0 : \ ((x > 1.0f) ? 255 : (uint8_t)(255.0f*(x) + 0.5f))) #define ROUNDCLAMP(x) ((x < 0.0) ? 0 : \ ((x > 1.0) ? 255 : (uint8_t)(255.0*(x) + 0.5))) /** @brief Case-insensitive test to see if String ends with Suffix */ static int StringEndsWith(const char *String, const char *Suffix) { unsigned i, StringLength = strlen(String), SuffixLength = strlen(Suffix); if(StringLength < SuffixLength) return 0; String += StringLength - SuffixLength; for(i = 0; i < SuffixLength; i++) if(tolower(String[i]) != tolower(Suffix[i])) return 0; return 1; } /** @brief Fill an image with a color */ static void FillImage(uint32_t *Image, int Width, int Height, uint32_t Color) { int x, y; if(Image) for(y = 0; y < Height; y++, Image += Width) for(x = 0; x < Width; x++) Image[x] = Color; } /** * @brief Check use of color and alpha, and count number of distinct colors * @param NumColors set by the routine to the number of unique colors * @param UseColor set to 1 if the image is not grayscale * @param UseAlpha set to 1 if the image alpha is not constant 255 * @param Image pointer to U8 RGBA interleaved image data * @param Width, Height dimensions of the image * @return pointer to a color palette with NumColors entries or NULL if the * number of distinct colors exceeds 256. * * This routine checks whether an RGBA image makes use of color and alpha, and * constructs a palette if the number of distinct colors is 256 or fewer. This * information is useful for writing image files with smaller file size. */ static uint32_t *GetImagePalette(int *NumColors, int *UseColor, int *UseAlpha, const uint32_t *Image, int Width, int Height) { const int MaxColors = 256; uint32_t *Palette = NULL; uint32_t Pixel; int x, y, i, Red, Green, Blue, Alpha; if(!UseColor || !NumColors || !UseAlpha) return NULL; else if(!Image || !(Palette = (uint32_t *)Malloc(sizeof(uint32_t)*MaxColors))) { *NumColors = -1; *UseColor = *UseAlpha = 1; return NULL; } *NumColors = *UseColor = *UseAlpha = 0; for(y = 0; y < Height; y++) { for(x = 0; x < Width; x++) { Pixel = *(Image++); Red = ((uint8_t *)&Pixel)[0]; Green = ((uint8_t *)&Pixel)[1]; Blue = ((uint8_t *)&Pixel)[2]; Alpha = ((uint8_t *)&Pixel)[3]; if(Red != Green || Red != Blue) /* Check color */ *UseColor = 1; if(Alpha != 255) /* Check alpha */ *UseAlpha = 1; /* Check Palette colors (if *NumColors != -1) */ for(i = 0; i < *NumColors; i++) if(Pixel == Palette[i]) break; if(i == *NumColors) { if(i < MaxColors) { /* Add new color to Palette */ Palette[i] = Pixel; (*NumColors)++; } else { /* Maximum size for Palette exceeded */ Free(Palette); Palette = NULL; *NumColors = -1; /* Don't check Palette colors */ } } } } return Palette; } /** @brief Read a 16-bit little Endian word from File */ static uint16_t ReadWordLE(FILE *File) { uint16_t w; w = (uint16_t) getc(File); w |= ((uint16_t) getc(File) << 8); return w; } /** @brief Read a 32-bit little Endian double word from File */ static uint32_t ReadDWordLE(FILE *File) { uint32_t dw; dw = (uint32_t) getc(File); dw |= ((uint32_t) getc(File) << 8); dw |= ((uint32_t) getc(File) << 16); dw |= ((uint32_t) getc(File) << 24); return dw; } /** @brief Write a 16-bit word in little Endian format */ static void WriteWordLE(uint16_t w, FILE *File) { putc(w & 0xFF, File); putc((w & 0xFF00) >> 8, File); } /** @brief Write a 32-bit double word in little Endian format */ static void WriteDWordLE(uint32_t dw, FILE *File) { putc(dw & 0xFF, File); putc((dw & 0xFF00) >> 8, File); putc((dw & 0xFF0000) >> 16, File); putc((dw & 0xFF000000) >> 24, File); } /** @brief Internal function for reading 1-bit BMP */ static int ReadBmp1Bit(uint32_t *Image, int Width, int Height, FILE *File, const uint32_t *Palette) { int RowPadding = (-(Width+7)/8)&3; int x, y, Bit; unsigned Code; Image += ((long int)Width)*((long int)Height - 1); for(y = Height; y; y--, Image -= Width) { if(feof(File)) return 0; for(x = 0; x < Width;) { Code = getc(File); for(Bit = 7; Bit >= 0 && x < Width; Bit--, Code <<= 1) Image[x++] = Palette[(Code & 0x80) ? 1:0]; } for(x = RowPadding; x; x--) getc(File); /* Skip padding bytes at the end of the row */ } return 1; } /** @brief Internal function for reading 4-bit BMP */ static int ReadBmp4Bit(uint32_t *Image, int Width, int Height, FILE *File, const uint32_t *Palette) { int RowPadding = (-(Width+1)/2)&3; int x, y; unsigned Code; Image += ((long int)Width)*((long int)Height - 1); for(y = Height; y; y--, Image -= Width) { if(feof(File)) return 0; for(x = 0; x < Width;) { Code = getc(File); Image[x++] = Palette[(Code & 0xF0) >> 4]; if(x < Width) Image[x++] = Palette[Code & 0x0F]; } for(x = RowPadding; x; x--) getc(File); /* Skip padding bytes at the end of the row */ } return 1; } /** @brief Internal function for reading 4-bit RLE-compressed BMP */ static int ReadBmp4BitRle(uint32_t *Image, int Width, int Height, FILE *File, const uint32_t *Palette) { int x, y, dy, k; unsigned Count, Value; uint32_t ColorH, ColorL; FillImage(Image, Width, Height, Palette[0]); Image += ((long int)Width)*((long int)Height - 1); for(x = 0, y = Height; y;) { if(feof(File)) return 0; Count = getc(File); Value = getc(File); if(!Count) { /* Count = 0 is the escape code */ switch(Value) { case 0: /* End of line */ Image -= Width; x = 0; y--; break; case 1: /* End of bitmap */ return 1; case 2: /* Delta */ x += getc(File); dy = getc(File); y -= dy; Image -= dy*Width; if(x >= Width || y < 0) return 0; break; default: /* Read a run of uncompressed data (Value = length of run) */ Count = k = Value; if(x >= Width) return 0; do { Value = getc(File); Image[x++] = Palette[(Value & 0xF0) >> 4]; if(x >= Width) break; if(--k) { Image[x++] = Palette[Value & 0x0F]; k--; if(x >= Width) break; } }while(k); if(((Count + 1)/2) & 1) getc(File); /* Padding for word align */ } } else { /* Run of pixels (Count = length of run) */ ColorH = Palette[(Value & 0xF0) >> 4]; ColorL = Palette[Value & 0xF]; if(x >= Width) return 0; do { Image[x++] = ColorH; Count--; if(x >= Width) break; if(Count) { Image[x++] = ColorL; Count--; if(x >= Width) break; } }while(Count); } } return 1; } /** @brief Internal function for reading 8-bit BMP */ static int ReadBmp8Bit(uint32_t *Image, int Width, int Height, FILE *File, const uint32_t *Palette) { int RowPadding = (-Width)&3; int x, y; Image += ((long int)Width)*((long int)Height - 1); for(y = Height; y; y--, Image -= Width) { if(feof(File)) return 0; for(x = 0; x < Width; x++) Image[x] = Palette[getc(File) & 0xFF]; for(x = RowPadding; x; x--) getc(File); /* Skip padding bytes at the end of the row */ } return 1; } /** @brief Internal function for reading 8-bit RLE-compressed BMP */ static int ReadBmp8BitRle(uint32_t *Image, int Width, int Height, FILE *File, const uint32_t *Palette) { int x, y, dy, k; unsigned Count, Value; uint32_t Color; FillImage(Image, Width, Height, Palette[0]); Image += ((long int)Width)*((long int)Height - 1); for(x = 0, y = Height; y;) { if(feof(File)) return 0; Count = getc(File); Value = getc(File); if(!Count) { /* Count = 0 is the escape code */ switch(Value) { case 0: /* End of line */ Image -= Width; x = 0; y--; break; case 1: /* End of bitmap */ return 1; case 2: /* Delta */ x += getc(File); dy = getc(File); y -= dy; Image -= dy*Width; if(x >= Width || y < 0) return 0; break; default: /* Read a run of uncompressed data (Value = length of run) */ Count = k = Value; do { if(x >= Width) break; Image[x++] = Palette[getc(File) & 0xFF]; }while(--k); if(Count&1) getc(File); /* Padding for word align */ } } else { /* Run of pixels equal to Value (Count = length of run) */ Color = Palette[Value & 0xFF]; do { if(x >= Width) break; Image[x++] = Color; }while(--Count); } } return 1; } /** @brief Internal function for reading 24-bit BMP */ static int ReadBmp24Bit(uint32_t *Image, int Width, int Height, FILE *File) { uint8_t *ImagePtr = (uint8_t *)Image; int RowPadding = (-3*Width)&3; int x, y; Width <<= 2; ImagePtr += ((long int)Width)*((long int)Height - 1); for(y = Height; y; y--, ImagePtr -= Width) { if(feof(File)) return 0; for(x = 0; x < Width; x += 4) { ImagePtr[x+3] = 255; /* Set alpha */ ImagePtr[x+2] = getc(File); /* Read blue component */ ImagePtr[x+1] = getc(File); /* Read green component */ ImagePtr[x+0] = getc(File); /* Read red component */ } for(x = RowPadding; x; x--) getc(File); /* Skip padding bytes at the end of the row */ } return 1; } /** @brief Internal function for determining bit shifts in bitfield BMP */ static void GetMaskShifts(uint32_t Mask, int *LeftShift, int *RightShift) { int Shift = 0, BitCount = 0; if(!Mask) { *LeftShift = 0; *RightShift = 0; return; } while(!(Mask & 1)) /* Find the first true bit */ { Mask >>= 1; ++Shift; } /* Adjust the result for scaling to 8-bit quantities */ while(Mask & 1) /* Count the number of true bits */ { Mask >>= 1; ++BitCount; } /* Compute a signed shift (right is positive) */ Shift += BitCount - 8; if(Shift >= 0) { *LeftShift = 0; *RightShift = Shift; } else { *LeftShift = -Shift; *RightShift = 0; } } /** @brief Internal function for reading 16-bit BMP */ static int ReadBmp16Bit(uint32_t *Image, int Width, int Height, FILE *File, uint32_t RedMask, uint32_t GreenMask, uint32_t BlueMask, uint32_t AlphaMask) { uint8_t *ImagePtr = (uint8_t *)Image; uint32_t Code; int RowPadding = (-2*Width)&3; int RedLeftShift, GreenLeftShift, BlueLeftShift, AlphaLeftShift; int RedRightShift, GreenRightShift, BlueRightShift, AlphaRightShift; int x, y; GetMaskShifts(RedMask, &RedLeftShift, &RedRightShift); GetMaskShifts(GreenMask, &GreenLeftShift, &GreenRightShift); GetMaskShifts(BlueMask, &BlueLeftShift, &BlueRightShift); GetMaskShifts(AlphaMask, &AlphaLeftShift, &AlphaRightShift); Width <<= 2; ImagePtr += ((long int)Width)*((long int)Height - 1); for(y = Height; y; y--, ImagePtr -= Width) { if(feof(File)) return 0; for(x = 0; x < Width; x += 4) { Code = ReadWordLE(File); /* By the Windows 4.x BMP specification, color component masks must be contiguous [http://www.fileformat.info/format/bmp/egff.htm]. So we can decode the bitfields by bitwise AND with the mask and applying a bitshift.*/ ImagePtr[x+3] = ((Code & AlphaMask) >> AlphaRightShift) << AlphaLeftShift; ImagePtr[x+2] = ((Code & BlueMask ) >> BlueRightShift ) << BlueLeftShift; ImagePtr[x+1] = ((Code & GreenMask) >> GreenRightShift) << GreenLeftShift; ImagePtr[x+0] = ((Code & RedMask ) >> RedRightShift ) << RedLeftShift; } for(x = RowPadding; x; x--) getc(File); /* Skip padding bytes at the end of the row */ } return 1; } /** @brief Internal function for reading 32-bit BMP */ static int ReadBmp32Bit(uint32_t *Image, int Width, int Height, FILE *File, uint32_t RedMask, uint32_t GreenMask, uint32_t BlueMask, uint32_t AlphaMask) { uint8_t *ImagePtr; uint32_t Code; int RedLeftShift, GreenLeftShift, BlueLeftShift, AlphaLeftShift; int RedRightShift, GreenRightShift, BlueRightShift, AlphaRightShift; int x, y; GetMaskShifts(RedMask, &RedLeftShift, &RedRightShift); GetMaskShifts(GreenMask, &GreenLeftShift, &GreenRightShift); GetMaskShifts(BlueMask, &BlueLeftShift, &BlueRightShift); GetMaskShifts(AlphaMask, &AlphaLeftShift, &AlphaRightShift); Width <<= 2; ImagePtr = (uint8_t *)Image + ((long int)Width)*((long int)Height - 1); for(y = Height; y; y--, ImagePtr -= Width) { if(feof(File)) return 0; for(x = 0; x < Width; x += 4) { Code = ReadDWordLE(File); /* By the Windows 4.x BMP specification, color component masks must be contiguous [http://www.fileformat.info/format/bmp/egff.htm]. So we can decode the bitfields by bitwise AND with the mask and applying a bitshift.*/ ImagePtr[x+3] = ((Code & AlphaMask) >> AlphaRightShift) << AlphaLeftShift; ImagePtr[x+2] = ((Code & BlueMask ) >> BlueRightShift ) << BlueLeftShift; ImagePtr[x+1] = ((Code & GreenMask) >> GreenRightShift) << GreenLeftShift; ImagePtr[x+0] = ((Code & RedMask ) >> RedRightShift ) << RedLeftShift; } } return 1; } /** * @brief Read a BMP (Windows Bitmap) image file as RGBA data * * @param Image, Width, Height pointers to be filled with the pointer * to the image data and the image dimensions. * @param File stdio FILE pointer pointing to the beginning of the BMP file * * @return 1 on success, 0 on failure * * This function is called by \c ReadImage to read BMP images. Before calling * \c ReadBmp, the caller should open \c File as a FILE pointer in binary read * mode. When \c ReadBmp is complete, the caller should close \c File. */ static int ReadBmp(uint32_t **Image, int *Width, int *Height, FILE *File) { uint32_t *Palette = NULL; uint8_t *PalettePtr; long int ImageDataOffset, InfoSize; unsigned i, NumPlanes, BitsPerPixel, Compression, NumColors; uint32_t RedMask, GreenMask, BlueMask, AlphaMask; int Success = 0, Os2Bmp; uint8_t Magic[2]; *Image = NULL; *Width = *Height = 0; fseek(File, 0, SEEK_SET); Magic[0] = getc(File); Magic[1] = getc(File); if(!(Magic[0] == 0x42 && Magic[1] == 0x4D) /* Verify the magic numbers */ || fseek(File, 8, SEEK_CUR)) /* Skip the reserved fields */ { ErrorMessage("Invalid BMP header.\n"); goto Catch; } ImageDataOffset = ReadDWordLE(File); InfoSize = ReadDWordLE(File); /* Read the info header */ if(InfoSize < 12) { ErrorMessage("Invalid BMP info header.\n"); goto Catch; } if((Os2Bmp = (InfoSize == 12))) /* This is an OS/2 V1 infoheader */ { *Width = (int)ReadWordLE(File); *Height = (int)ReadWordLE(File); NumPlanes = (unsigned)ReadWordLE(File); BitsPerPixel = (unsigned)ReadWordLE(File); Compression = 0; NumColors = 0; RedMask = 0x00FF0000; GreenMask = 0x0000FF00; BlueMask = 0x000000FF; AlphaMask = 0xFF000000; } else { *Width = abs((int)ReadDWordLE(File)); *Height = abs((int)ReadDWordLE(File)); NumPlanes = (unsigned)ReadWordLE(File); BitsPerPixel = (unsigned)ReadWordLE(File); Compression = (unsigned)ReadDWordLE(File); fseek(File, 12, SEEK_CUR); NumColors = (unsigned)ReadDWordLE(File); fseek(File, 4, SEEK_CUR); RedMask = ReadDWordLE(File); GreenMask = ReadDWordLE(File); BlueMask = ReadDWordLE(File); AlphaMask = ReadDWordLE(File); } /* Check for problems or unsupported compression modes */ if(*Width > MAX_IMAGE_SIZE || *Height > MAX_IMAGE_SIZE) { ErrorMessage("Image dimensions exceed MAX_IMAGE_SIZE.\n"); goto Catch; } if(feof(File) || NumPlanes != 1 || Compression > 3) goto Catch; /* Allocate the image data */ if(!(*Image = (uint32_t *)Malloc(sizeof(uint32_t)*((long int)*Width)*((long int)*Height)))) goto Catch; /* Read palette */ if(BitsPerPixel <= 8) { fseek(File, 14 + InfoSize, SEEK_SET); if(!NumColors) NumColors = 1 << BitsPerPixel; if(!(Palette = (uint32_t *)Malloc(sizeof(uint32_t)*256))) goto Catch; for(i = 0, PalettePtr = (uint8_t *)Palette; i < NumColors; i++) { PalettePtr[3] = 255; /* Set alpha */ PalettePtr[2] = getc(File); /* Read blue component */ PalettePtr[1] = getc(File); /* Read green component */ PalettePtr[0] = getc(File); /* Read red component */ PalettePtr += 4; if(!Os2Bmp) getc(File); /* Skip extra byte (for non-OS/2 bitmaps) */ } for(; i < 256; i++) /* Fill the rest of the palette with the first color */ Palette[i] = Palette[0]; } if(fseek(File, ImageDataOffset, SEEK_SET) || feof(File)) { ErrorMessage("File error.\n"); goto Catch; } /*** Read the bitmap image data ***/ switch(Compression) { case 0: /* Uncompressed data */ switch(BitsPerPixel) { case 1: /* Read 1-bit uncompressed indexed data */ Success = ReadBmp1Bit(*Image, *Width, *Height, File, Palette); break; case 4: /* Read 4-bit uncompressed indexed data */ Success = ReadBmp4Bit(*Image, *Width, *Height, File, Palette); break; case 8: /* Read 8-bit uncompressed indexed data */ Success = ReadBmp8Bit(*Image, *Width, *Height, File, Palette); break; case 24: /* Read 24-bit BGR image data */ Success = ReadBmp24Bit(*Image, *Width, *Height, File); break; case 16: /* Read 16-bit data */ Success = ReadBmp16Bit(*Image, *Width, *Height, File, 0x001F << 10, 0x001F << 5, 0x0001F, 0); break; case 32: /* Read 32-bit BGRA image data */ Success = ReadBmp32Bit(*Image, *Width, *Height, File, 0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000); break; } break; case 1: /* 8-bit RLE */ if(BitsPerPixel == 8) Success = ReadBmp8BitRle(*Image, *Width, *Height, File, Palette); break; case 2: /* 4-bit RLE */ if(BitsPerPixel == 4) Success = ReadBmp4BitRle(*Image, *Width, *Height, File, Palette); break; case 3: /* Bitfields data */ switch(BitsPerPixel) { case 16: /* Read 16-bit bitfields data */ Success = ReadBmp16Bit(*Image, *Width, *Height, File, RedMask, GreenMask, BlueMask, AlphaMask); break; case 32: /* Read 32-bit bitfields data */ Success = ReadBmp32Bit(*Image, *Width, *Height, File, RedMask, GreenMask, BlueMask, AlphaMask); break; } break; } if(!Success) ErrorMessage("Error reading BMP data.\n"); Catch: /* There was a problem, clean up and exit */ if(Palette) Free(Palette); if(!Success && *Image) Free(*Image); return Success; } /** * @brief Write a BMP image * * @param Image pointer to RGBA image data * @param Width, Height the image dimensions * @param File stdio FILE pointer * * @return 1 on success, 0 on failure * * This function is called by \c WriteImage to write BMP images. The caller * should open \c File in binary write mode. When \c WriteBmp is complete, * the caller should close \c File. * * The image is generally saved in uncompressed 24-bit RGB format. But where * possible, the image is saved using an 8-bit palette for a substantial * decrease in file size. The image data is always saved losslessly. * * @note The alpha channel is lost when saving to BMP. It is possible to write * the alpha channel in a 32-bit BMP image, however, such images are not * widely supported. RGB 24-bit BMP on the other hand is well supported. */ static int WriteBmp(const uint32_t *Image, int Width, int Height, FILE *File) { const uint8_t *ImagePtr = (uint8_t *)Image; uint32_t *Palette = NULL; uint32_t Pixel; long int ImageSize; int UsePalette, NumColors, UseColor, UseAlpha; int x, y, i, RowPadding, Success = 0; if(!Image) return 0; Palette = GetImagePalette(&NumColors, &UseColor, &UseAlpha, Image, Width, Height); /* Decide whether to use 8-bit palette or 24-bit RGB format */ if(Palette && 2*NumColors < Width*Height) UsePalette = 1; else UsePalette = NumColors = 0; /* Tell File to use buffering */ setvbuf(File, 0, _IOFBF, FILE_BUFFER_CAPACITY); if(UsePalette) { RowPadding = (-Width)&3; ImageSize = (Width + RowPadding)*((long int)Height); } else { RowPadding = (-3*Width)&3; ImageSize = (3*Width + RowPadding)*((long int)Height); } /*** Write the header ***/ /* Write the BMP header */ putc(0x42, File); /* Magic numbers */ putc(0x4D, File); /* Filesize */ WriteDWordLE(54 + 4*NumColors + ImageSize, File); WriteDWordLE(0, File); /* Reserved fields */ WriteDWordLE(54 + 4*NumColors, File); /* Image data offset */ /* Write the infoheader */ WriteDWordLE(40, File); /* Infoheader size */ WriteDWordLE(Width, File); /* Image width */ WriteDWordLE(Height, File); /* Image height */ WriteWordLE(1, File); /* Number of colorplanes */ WriteWordLE((UsePalette) ? 8:24, File); /* Bits per pixel */ WriteDWordLE(0, File); /* Compression method (none) */ WriteDWordLE(ImageSize, File); /* Image size */ WriteDWordLE(2835, File); /* HResolution (2835=72dpi) */ WriteDWordLE(2835, File); /* VResolution */ /* Number of colors */ WriteDWordLE((!UsePalette || NumColors == 256) ? 0:NumColors, File); WriteDWordLE(0, File); /* Important colors */ if(ferror(File)) { ErrorMessage("Error during write to file.\n"); goto Catch; } if(UsePalette) { /* Write the Palette */ for(i = 0; i < NumColors; i++) { Pixel = Palette[i]; putc(((uint8_t *)&Pixel)[2], File); /* Blue */ putc(((uint8_t *)&Pixel)[1], File); /* Green */ putc(((uint8_t *)&Pixel)[0], File); /* Red */ putc(0, File); /* Unused */ } } /* Write the image data */ Width <<= 2; ImagePtr += ((long int)Width)*((long int)Height - 1); for(y = Height; y; y--, ImagePtr -= Width) { if(UsePalette) { /* 8-bit palette image data */ for(x = 0; x < Width; x += 4) { Pixel = *((uint32_t *)(ImagePtr + x)); for(i = 0; i < NumColors; i++) if(Pixel == Palette[i]) break; putc(i, File); } } else { /* 24-bit RGB image data */ for(x = 0; x < Width; x += 4) { putc(ImagePtr[x+2], File); /* Write blue component */ putc(ImagePtr[x+1], File); /* Write green component */ putc(ImagePtr[x+0], File); /* Write red component */ } } for(x = RowPadding; x; x--) /* Write row padding */ putc(0, File); } if(ferror(File)) { ErrorMessage("Error during write to file.\n"); goto Catch; } Success = 1; Catch: if(Palette) Free(Palette); return Success; } #ifdef USE_LIBJPEG /** * @brief Struct that assists in customizing libjpeg error management * * This struct is used in combination with JerrExit (static function defined * here in utiljpeg.c) to have control over how libjpeg errors are displayed. */ typedef struct{ struct jpeg_error_mgr pub; jmp_buf jmpbuf; } hooked_jerr; /** @brief Callback for displaying libjpeg errors */ METHODDEF(void) JerrExit(j_common_ptr cinfo) { hooked_jerr *Jerr = (hooked_jerr *) cinfo->err; (*cinfo->err->output_message)(cinfo); longjmp(Jerr->jmpbuf, 1); } /** * @brief Read a JPEG (Joint Picture Experts Group) image file as RGBA data * * @param Image, Width, Height pointers to be filled with the pointer * to the image data and the image dimensions. * @param File stdio FILE pointer pointing to the beginning of the BMP file * * @return 1 on success, 0 on failure * * This function is called by \c ReadImage to read JPEG images. Before calling * \c ReadJpeg, the caller should open \c File as a FILE pointer in binary read * mode. When \c ReadJpeg is complete, the caller should close \c File. */ static int ReadJpeg(uint32_t **Image, int *Width, int *Height, FILE *File) { struct jpeg_decompress_struct cinfo; hooked_jerr Jerr; JSAMPARRAY Buffer; uint8_t *ImagePtr; unsigned i, RowSize; *Image = 0; *Width = *Height = 0; cinfo.err = jpeg_std_error(&Jerr.pub); Jerr.pub.error_exit = JerrExit; if(setjmp(Jerr.jmpbuf)) goto Catch; /* If this code is reached, libjpeg has signaled an error. */ jpeg_create_decompress(&cinfo); jpeg_stdio_src(&cinfo, File); jpeg_read_header(&cinfo, 1); cinfo.out_color_space = JCS_RGB; /* Ask for RGB image data */ jpeg_start_decompress(&cinfo); *Width = (int)cinfo.output_width; *Height = (int)cinfo.output_height; if(*Width > MAX_IMAGE_SIZE || *Height > MAX_IMAGE_SIZE) { ErrorMessage("Image dimensions exceed MAX_IMAGE_SIZE.\n"); jpeg_abort_decompress(&cinfo); goto Catch; } /* Allocate image memory */ if(!(*Image = (uint32_t *)Malloc(sizeof(uint32_t) *((size_t)*Width)*((size_t)*Height)))) { jpeg_abort_decompress(&cinfo); goto Catch; } /* Allocate a one-row-high array that will go away when done */ RowSize = cinfo.output_width * cinfo.output_components; Buffer = (*cinfo.mem->alloc_sarray) ((j_common_ptr) &cinfo, JPOOL_IMAGE, RowSize, 1); ImagePtr = (uint8_t *)*Image; while(cinfo.output_scanline < cinfo.output_height) for(jpeg_read_scanlines(&cinfo, Buffer, 1), i = 0; i < RowSize; i += 3) { *(ImagePtr++) = Buffer[0][i]; /* Red */ *(ImagePtr++) = Buffer[0][i+1]; /* Green */ *(ImagePtr++) = Buffer[0][i+2]; /* Blue */ *(ImagePtr++) = 0xFF; } jpeg_finish_decompress(&cinfo); jpeg_destroy_decompress(&cinfo); return 1; Catch: if(*Image) Free(*Image); *Width = *Height = 0; jpeg_destroy_decompress(&cinfo); return 0; } /** * @brief Write a JPEG image as RGB data * * @param Image pointer to RGBA image data * @param Width, Height the image dimensions * @param File stdio FILE pointer * * @return 1 on success, 0 on failure * * This function is called by \c WriteImage to write JPEG images. The caller * should open \c File in binary write mode. When \c WriteJpeg is complete, * the caller should close \c File. * * @note The alpha channel is lost when saving to JPEG since the JPEG format * does not support RGBA images. (It is in principle possible to store * four channels in a JPEG as a CMYK image, but storing alpha this way * is strange.) */ static int WriteJpeg(const uint32_t *Image, int Width, int Height, FILE *File, int Quality) { struct jpeg_compress_struct cinfo; hooked_jerr Jerr; uint8_t *Buffer = 0, *ImagePtr; unsigned i, RowSize; if(!Image) return 0; cinfo.err = jpeg_std_error(&Jerr.pub); Jerr.pub.error_exit = JerrExit; if(setjmp(Jerr.jmpbuf)) goto Catch; /* If this code is reached, libjpeg has signaled an error. */ jpeg_create_compress(&cinfo); jpeg_stdio_dest(&cinfo, File); cinfo.image_width = Width; cinfo.image_height = Height; cinfo.input_components = 3; cinfo.in_color_space = JCS_RGB; jpeg_set_defaults(&cinfo); jpeg_set_quality(&cinfo, (Quality < 100) ? Quality : 100, 1); jpeg_start_compress(&cinfo, 1); RowSize = 3*Width; ImagePtr = (uint8_t *)Image; if(!(Buffer = (uint8_t *)Malloc(RowSize))) goto Catch; while(cinfo.next_scanline < cinfo.image_height) { for(i = 0; i < RowSize; i += 3) { Buffer[i] = ImagePtr[0]; /* Red */ Buffer[i+1] = ImagePtr[1]; /* Green */ Buffer[i+2] = ImagePtr[2]; /* Blue */ ImagePtr += 4; } jpeg_write_scanlines(&cinfo, &Buffer, 1); } if(Buffer) Free(Buffer); jpeg_finish_compress(&cinfo); jpeg_destroy_compress(&cinfo); return 1; Catch: if(Buffer) Free(Buffer); jpeg_destroy_compress(&cinfo); return 0; } #endif /* USE_LIBJPEG */ #ifdef USE_LIBPNG /** * @brief Read a PNG (Portable Network Graphics) image file as RGBA data * * @param Image, Width, Height pointers to be filled with the pointer * to the image data and the image dimensions. * @param File stdio FILE pointer pointing to the beginning of the PNG file * * @return 1 on success, 0 on failure * * This function is called by \c ReadImage to read PNG images. Before calling * \c ReadPng, the caller should open \c File as a FILE pointer in binary read * mode. When \c ReadPng is complete, the caller should close \c File. */ static int ReadPng(uint32_t **Image, int *Width, int *Height, FILE *File) { png_bytep *RowPointers; png_byte Header[8]; png_structp Png; png_infop Info; png_uint_32 PngWidth, PngHeight; int BitDepth, ColorType, InterlaceType; unsigned Row; *Image = 0; *Width = *Height = 0; /* Check that file is a PNG file */ if(fread(Header, 1, 8, File) != 8 || png_sig_cmp(Header, 0, 8)) return 0; /* Read the info header */ if(!(Png = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL)) || !(Info = png_create_info_struct(Png))) { if(Png) png_destroy_read_struct(&Png, (png_infopp)NULL, (png_infopp)NULL); return 0; } if(setjmp(png_jmpbuf(Png))) goto Catch; /* If this code is reached, libpng has signaled an error. */ png_init_io(Png, File); png_set_sig_bytes(Png, 8); png_set_user_limits(Png, MAX_IMAGE_SIZE, MAX_IMAGE_SIZE); png_read_info(Png, Info); png_get_IHDR(Png, Info, &PngWidth, &PngHeight, &BitDepth, &ColorType, &InterlaceType, (int*)NULL, (int*)NULL); *Width = (int)PngWidth; *Height = (int)PngHeight; /* Tell libpng to convert everything to 32-bit RGBA */ if(ColorType == PNG_COLOR_TYPE_PALETTE) png_set_palette_to_rgb(Png); if(ColorType == PNG_COLOR_TYPE_GRAY && BitDepth < 8) png_set_expand_gray_1_2_4_to_8(Png); if(ColorType == PNG_COLOR_TYPE_GRAY || ColorType == PNG_COLOR_TYPE_GRAY_ALPHA) png_set_gray_to_rgb(Png); if(png_get_valid(Png, Info, PNG_INFO_tRNS)) png_set_tRNS_to_alpha(Png); png_set_strip_16(Png); png_set_filler(Png, 0xFF, PNG_FILLER_AFTER); png_set_interlace_handling(Png); png_read_update_info(Png, Info); /* Allocate image memory and row pointers */ if(!(*Image = (uint32_t *)Malloc(sizeof(uint32_t) *((size_t)*Width)*((size_t)*Height))) || !(RowPointers = (png_bytep *)Malloc(sizeof(png_bytep) *PngHeight))) goto Catch; for(Row = 0; Row < PngHeight; Row++) RowPointers[Row] = (png_bytep)(*Image + PngWidth*Row); /* Read the image data */ png_read_image(Png, RowPointers); Free(RowPointers); png_destroy_read_struct(&Png, &Info, (png_infopp)NULL); return 1; Catch: if(*Image) Free(*Image); *Width = *Height = 0; png_destroy_read_struct(&Png, &Info, (png_infopp)NULL); return 0; } /** * @brief Write a PNG image * * @param Image pointer to RGBA image data * @param Width, Height the image dimensions * @param File stdio FILE pointer * * @return 1 on success, 0 on failure * * This function is called by \c WriteImage to write PNG images. The caller * should open \c File in binary write mode. When \c WritePng is complete, * the caller should close \c File. * * The image is written as 8-bit grayscale, indexed (PLTE), indexed with * transparent colors (PLTE+tRNS), RGB, or RGBA data (in that order of * preference) depending on the image data to encourage smaller file size. The * image data is always saved losslessly. In principle, PNG can also make use * of the pixel bit depth (1, 2, 4, 8, or 16) to reduce the file size further, * but it is not done here. */ static int WritePng(const uint32_t *Image, int Width, int Height, FILE *File) { const uint32_t *ImagePtr; uint32_t *Palette = NULL; uint8_t *RowBuffer; png_structp Png; png_infop Info; png_color PngPalette[256]; png_byte PngTrans[256]; uint32_t Pixel; int PngColorType, NumColors, UseColor, UseAlpha; int x, y, i, Success = 0; if(!Image) return 0; if(!(RowBuffer = (uint8_t *)Malloc(4*Width))) return 0; if(!(Png = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL)) || !(Info = png_create_info_struct(Png))) { if(Png) png_destroy_write_struct(&Png, (png_infopp)NULL); Free(RowBuffer); return 0; } if(setjmp(png_jmpbuf(Png))) { /* If this code is reached, libpng has signaled an error. */ goto Catch; } /* Configure PNG output */ png_init_io(Png, File); png_set_compression_level(Png, Z_BEST_COMPRESSION); Palette = GetImagePalette(&NumColors, &UseColor, &UseAlpha, Image, Width, Height); /* The PNG image is written according to the analysis of GetImagePalette */ if(Palette && UseColor) PngColorType = PNG_COLOR_TYPE_PALETTE; else if(UseAlpha) PngColorType = PNG_COLOR_TYPE_RGB_ALPHA; else if(UseColor) PngColorType = PNG_COLOR_TYPE_RGB; else PngColorType = PNG_COLOR_TYPE_GRAY; png_set_IHDR(Png, Info, Width, Height, 8, PngColorType, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE); if(PngColorType == PNG_COLOR_TYPE_PALETTE) { for(i = 0; i < NumColors; i++) { Pixel = Palette[i]; PngPalette[i].red = ((uint8_t *)&Pixel)[0]; PngPalette[i].green = ((uint8_t *)&Pixel)[1]; PngPalette[i].blue = ((uint8_t *)&Pixel)[2]; PngTrans[i] = ((uint8_t *)&Pixel)[3]; } png_set_PLTE(Png, Info, PngPalette, NumColors); if(UseAlpha) png_set_tRNS(Png, Info, PngTrans, NumColors, NULL); } png_write_info(Png, Info); for(y = 0, ImagePtr = Image; y < Height; y++, ImagePtr += Width) { switch(PngColorType) { case PNG_COLOR_TYPE_RGB_ALPHA: png_write_row(Png, (png_bytep)ImagePtr); break; case PNG_COLOR_TYPE_RGB: for(x = 0; x < Width; x++) { Pixel = ImagePtr[x]; RowBuffer[3*x + 0] = ((uint8_t *)&Pixel)[0]; RowBuffer[3*x + 1] = ((uint8_t *)&Pixel)[1]; RowBuffer[3*x + 2] = ((uint8_t *)&Pixel)[2]; } png_write_row(Png, (png_bytep)RowBuffer); break; case PNG_COLOR_TYPE_GRAY: for(x = 0; x < Width; x++) { Pixel = ImagePtr[x]; RowBuffer[x] = ((uint8_t *)&Pixel)[0]; } png_write_row(Png, (png_bytep)RowBuffer); break; case PNG_COLOR_TYPE_PALETTE: for(x = 0; x < Width; x++) { Pixel = ImagePtr[x]; for(i = 0; i < NumColors; i++) if(Pixel == Palette[i]) break; RowBuffer[x] = i; } png_write_row(Png, (png_bytep)RowBuffer); break; } } png_write_end(Png, Info); Success = 1; Catch: if(Palette) Free(Palette); png_destroy_write_struct(&Png, &Info); Free(RowBuffer); return Success; } #endif /* USE_LIBPNG */ #ifdef USE_LIBTIFF /** * @brief Read a TIFF (Tagged Information File Format) image file as RGBA data * * @param Image, Width, Height pointers to be filled with the pointer * to the image data and the image dimensions. * @param File stdio FILE pointer pointing to the beginning of the PNG file * * @return 1 on success, 0 on failure * * This function is called by \c ReadImage to read TIFF images. Before calling * \c ReadTiff, the caller should open \c File as a FILE pointer in binary read * mode. When \c ReadTiff is complete, the caller should close \c File. */ static int ReadTiff(uint32_t **Image, int *Width, int *Height, const char *FileName, unsigned Directory) { TIFF *Tiff; uint32 ImageWidth, ImageHeight; *Image = 0; *Width = *Height = 0; if(!(Tiff = TIFFOpen(FileName, "r"))) { ErrorMessage("TIFFOpen failed to open file.\n"); return 0; } TIFFSetDirectory(Tiff, Directory); TIFFGetField(Tiff, TIFFTAG_IMAGEWIDTH, &ImageWidth); TIFFGetField(Tiff, TIFFTAG_IMAGELENGTH, &ImageHeight); *Width = (int)ImageWidth; *Height = (int)ImageHeight; if(*Width > MAX_IMAGE_SIZE || *Height > MAX_IMAGE_SIZE) { ErrorMessage("Image dimensions exceed MAX_IMAGE_SIZE.\n"); goto Catch; } if(!(*Image = (uint32_t *)Malloc(sizeof(uint32_t)*ImageWidth*ImageHeight))) goto Catch; if(!TIFFReadRGBAImageOriented(Tiff, ImageWidth, ImageHeight, (uint32 *)*Image, ORIENTATION_TOPLEFT, 1)) goto Catch; TIFFClose(Tiff); return 1; Catch: if(*Image) Free(*Image); *Width = *Height = 0; TIFFClose(Tiff); return 0; } /** * @brief Write a TIFF image as RGBA data * * @param Image pointer to RGBA image data * @param Width, Height the image dimensions * @param File stdio FILE pointer * * @return 1 on success, 0 on failure * * This function is called by \c WriteImage to write TIFF images. The caller * should open \c File in binary write mode. When \c WriteTiff is complete, * the caller should close \c File. */ static int WriteTiff(const uint32_t *Image, int Width, int Height, const char *FileName) { TIFF *Tiff; uint16 Alpha = EXTRASAMPLE_ASSOCALPHA; if(!Image) return 0; if(!(Tiff = TIFFOpen(FileName, "w"))) { ErrorMessage("TIFFOpen failed to open file.\n"); return 0; } if(TIFFSetField(Tiff, TIFFTAG_IMAGEWIDTH, Width) != 1 || TIFFSetField(Tiff, TIFFTAG_IMAGELENGTH, Height) != 1 || TIFFSetField(Tiff, TIFFTAG_SAMPLESPERPIXEL, 4) != 1 || TIFFSetField(Tiff, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB) != 1 || TIFFSetField(Tiff, TIFFTAG_EXTRASAMPLES, 1, &Alpha) != 1 || TIFFSetField(Tiff, TIFFTAG_BITSPERSAMPLE, 8) != 1 || TIFFSetField(Tiff, TIFFTAG_ORIENTATION, ORIENTATION_TOPLEFT) != 1 || TIFFSetField(Tiff, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG) != 1 /* Compression can be COMPRESSION_NONE, COMPRESSION_DEFLATE, COMPRESSION_LZW, or COMPRESSION_JPEG */ || TIFFSetField(Tiff, TIFFTAG_COMPRESSION, COMPRESSION_LZW) != 1) { ErrorMessage("TIFFSetField failed.\n"); TIFFClose(Tiff); return 0; } if(TIFFWriteEncodedStrip(Tiff, 0, (tdata_t)Image, 4*((size_t)Width)*((size_t)Height)) < 0) { ErrorMessage("Error writing data to file.\n"); TIFFClose(Tiff); return 0; } TIFFClose(Tiff); return 1; } #endif /* USE_LIBTIFF */ /** @brief Convert from RGBA U8 to a specified format */ static void *ConvertToFormat(uint32_t *Src, int Width, int Height, unsigned Format) { const int NumPixels = Width*Height; const int NumChannels = (Format & IMAGEIO_GRAYSCALE) ? 1 : ((Format & IMAGEIO_STRIP_ALPHA) ? 3 : 4); const int ChannelStride = (Format & IMAGEIO_PLANAR) ? NumPixels : 1; const int ChannelStride2 = 2*ChannelStride; const int ChannelStride3 = 3*ChannelStride; double *DestD; float *DestF; uint8_t *DestU8; uint32_t Pixel; int Order[4] = {0, 1, 2, 3}; int i, x, y, PixelStride, RowStride; PixelStride = (Format & IMAGEIO_PLANAR) ? 1 : NumChannels; if(Format & IMAGEIO_COLUMNMAJOR) { RowStride = PixelStride; PixelStride *= Height; } else RowStride = Width*PixelStride; if(Format & IMAGEIO_BGRFLIP) { Order[0] = 2; Order[2] = 0; } if((Format & IMAGEIO_AFLIP) && !(Format & IMAGEIO_STRIP_ALPHA)) { Order[3] = Order[2]; Order[2] = Order[1]; Order[1] = Order[0]; Order[0] = 3; } switch(Format & (IMAGEIO_U8 | IMAGEIO_SINGLE | IMAGEIO_DOUBLE)) { case IMAGEIO_U8: /* Destination type is uint8_t */ if(!(DestU8 = (uint8_t *)Malloc(sizeof(uint8_t)*NumChannels*NumPixels))) return NULL; switch(NumChannels) { case 1: /* Convert RGBA U8 to grayscale U8 */ for(y = 0; y < Height; y++, Src += Width) for(x = 0, i = RowStride*y; x < Width; x++, i += PixelStride) { Pixel = Src[x]; DestU8[i] = (uint8_t)(0.299f*((uint8_t *)&Pixel)[0] + 0.587f*((uint8_t *)&Pixel)[1] + 0.114f*((uint8_t *)&Pixel)[2] + 0.5f); } break; case 3: /* Convert RGBA U8 to RGB (or BGR) U8 */ for(y = 0; y < Height; y++, Src += Width) for(x = 0, i = RowStride*y; x < Width; x++, i += PixelStride) { Pixel = Src[x]; DestU8[i] = ((uint8_t *)&Pixel)[Order[0]]; DestU8[i + ChannelStride] = ((uint8_t *)&Pixel)[Order[1]]; DestU8[i + ChannelStride2] = ((uint8_t *)&Pixel)[Order[2]]; } break; case 4: /* Convert RGBA U8 to RGBA (or BGRA, ARGB, or ABGR) U8 */ for(y = 0; y < Height; y++, Src += Width) for(x = 0, i = RowStride*y; x < Width; x++, i += PixelStride) { Pixel = Src[x]; DestU8[i] = ((uint8_t *)&Pixel)[Order[0]]; DestU8[i + ChannelStride] = ((uint8_t *)&Pixel)[Order[1]]; DestU8[i + ChannelStride2] = ((uint8_t *)&Pixel)[Order[2]]; DestU8[i + ChannelStride3] = ((uint8_t *)&Pixel)[Order[3]]; } break; } return DestU8; case IMAGEIO_SINGLE: /* Destination type is float */ if(!(DestF = (float *)Malloc(sizeof(float)*NumChannels*NumPixels))) return NULL; switch(NumChannels) { case 1: /* Convert RGBA U8 to grayscale float */ for(y = 0; y < Height; y++, Src += Width) for(x = 0, i = RowStride*y; x < Width; x++, i += PixelStride) { Pixel = Src[x]; DestF[i] = 1.172549019607843070675535e-3f*((uint8_t *)&Pixel)[0] + 2.301960784313725357840079e-3f*((uint8_t *)&Pixel)[1] + 4.470588235294117808150007e-4f*((uint8_t *)&Pixel)[2]; } break; case 3: /* Convert RGBA U8 to RGB (or BGR) float */ for(y = 0; y < Height; y++, Src += Width) for(x = 0, i = RowStride*y; x < Width; x++, i += PixelStride) { Pixel = Src[x]; DestF[i] = ((uint8_t *)&Pixel)[Order[0]]/255.0f; DestF[i + ChannelStride] = ((uint8_t *)&Pixel)[Order[1]]/255.0f; DestF[i + ChannelStride2] = ((uint8_t *)&Pixel)[Order[2]]/255.0f; } break; case 4: /* Convert RGBA U8 to RGBA (or BGRA, ARGB, or ABGR) float */ for(y = 0; y < Height; y++, Src += Width) for(x = 0, i = RowStride*y; x < Width; x++, i += PixelStride) { Pixel = Src[x]; DestF[i] = ((uint8_t *)&Pixel)[Order[0]]/255.0f; DestF[i + ChannelStride] = ((uint8_t *)&Pixel)[Order[1]]/255.0f; DestF[i + ChannelStride2] = ((uint8_t *)&Pixel)[Order[2]]/255.0f; DestF[i + ChannelStride3] = ((uint8_t *)&Pixel)[Order[3]]/255.0f; } break; } return DestF; case IMAGEIO_DOUBLE: /* Destination type is double */ if(!(DestD = (double *)Malloc(sizeof(double)*NumChannels*NumPixels))) return NULL; switch(NumChannels) { case 1: /* Convert RGBA U8 to grayscale double */ for(y = 0; y < Height; y++, Src += Width) for(x = 0, i = RowStride*y; x < Width; x++, i += PixelStride) { Pixel = Src[x]; DestD[i] = 1.172549019607843070675535e-3*((uint8_t *)&Pixel)[0] + 2.301960784313725357840079e-3*((uint8_t *)&Pixel)[1] + 4.470588235294117808150007e-4*((uint8_t *)&Pixel)[2]; } break; case 3: /* Convert RGBA U8 to RGB (or BGR) double */ for(y = 0; y < Height; y++, Src += Width) for(x = 0, i = RowStride*y; x < Width; x++, i += PixelStride) { Pixel = Src[x]; DestD[i] = ((uint8_t *)&Pixel)[Order[0]]/255.0; DestD[i + ChannelStride] = ((uint8_t *)&Pixel)[Order[1]]/255.0; DestD[i + ChannelStride2] = ((uint8_t *)&Pixel)[Order[2]]/255.0; } break; case 4: /* Convert RGBA U8 to RGBA (or BGRA, ARGB, or ABGR) double */ for(y = 0; y < Height; y++, Src += Width) for(x = 0, i = RowStride*y; x < Width; x++, i += PixelStride) { Pixel = Src[x]; DestD[i] = ((uint8_t *)&Pixel)[Order[0]]/255.0; DestD[i + ChannelStride] = ((uint8_t *)&Pixel)[Order[1]]/255.0; DestD[i + ChannelStride2] = ((uint8_t *)&Pixel)[Order[2]]/255.0; DestD[i + ChannelStride3] = ((uint8_t *)&Pixel)[Order[3]]/255.0; } break; } return DestD; default: return NULL; } } /** @brief Convert from a specified format to RGBA U8 */ static uint32_t *ConvertFromFormat(void *Src, int Width, int Height, unsigned Format) { const int NumPixels = Width*Height; const int NumChannels = (Format & IMAGEIO_GRAYSCALE) ? 1 : ((Format & IMAGEIO_STRIP_ALPHA) ? 3 : 4); const int ChannelStride = (Format & IMAGEIO_PLANAR) ? NumPixels : 1; const int ChannelStride2 = 2*ChannelStride; const int ChannelStride3 = 3*ChannelStride; double *SrcD = (double *)Src; float *SrcF = (float *)Src; uint8_t *SrcU8 = (uint8_t *)Src; uint8_t *Dest, *DestPtr; int Order[4] = {0, 1, 2, 3}; int i, x, y, PixelStride, RowStride; if(!(Dest = (uint8_t *)Malloc(sizeof(uint32_t)*NumPixels))) return NULL; DestPtr = Dest; PixelStride = (Format & IMAGEIO_PLANAR) ? 1 : NumChannels; if(Format & IMAGEIO_COLUMNMAJOR) { RowStride = PixelStride; PixelStride *= Height; } else RowStride = Width*PixelStride; if(Format & IMAGEIO_BGRFLIP) { Order[0] = 2; Order[2] = 0; } if((Format & IMAGEIO_AFLIP) && !(Format & IMAGEIO_STRIP_ALPHA)) { Order[3] = Order[2]; Order[2] = Order[1]; Order[1] = Order[0]; Order[0] = 3; } switch(Format & (IMAGEIO_U8 | IMAGEIO_SINGLE | IMAGEIO_DOUBLE)) { case IMAGEIO_U8: /* Source type is uint8_t */ switch(NumChannels) { case 1: /* Convert grayscale U8 to RGBA U8 */ for(y = 0; y < Height; y++, DestPtr += 4*Width) for(x = 0, i = RowStride*y; x < Width; x++, i += PixelStride) { DestPtr[4*x] = DestPtr[4*x + 1] = DestPtr[4*x + 2] = SrcU8[i]; DestPtr[4*x + 3] = 255; } break; case 3: /* Convert RGB (or BGR) U8 to RGBA U8 */ for(y = 0; y < Height; y++, DestPtr += 4*Width) for(x = 0, i = RowStride*y; x < Width; x++, i += PixelStride) { DestPtr[4*x + Order[0]] = SrcU8[i]; DestPtr[4*x + Order[1]] = SrcU8[i + ChannelStride]; DestPtr[4*x + Order[2]] = SrcU8[i + ChannelStride2]; DestPtr[4*x + 3] = 255; } break; case 4: /* Convert RGBA U8 to RGBA (or BGRA, ARGB, or ABGR) U8 */ for(y = 0; y < Height; y++, DestPtr += 4*Width) for(x = 0, i = RowStride*y; x < Width; x++, i += PixelStride) { DestPtr[4*x + Order[0]] = SrcU8[i]; DestPtr[4*x + Order[1]] = SrcU8[i + ChannelStride]; DestPtr[4*x + Order[2]] = SrcU8[i + ChannelStride2]; DestPtr[4*x + Order[3]] = SrcU8[i + ChannelStride3]; } break; } break; case IMAGEIO_SINGLE: /* Source type is float */ switch(NumChannels) { case 1: /* Convert grayscale float to RGBA U8 */ for(y = 0; y < Height; y++, DestPtr += 4*Width) for(x = 0, i = RowStride*y; x < Width; x++, i += PixelStride) { DestPtr[4*x] = DestPtr[4*x + 1] = DestPtr[4*x + 2] = ROUNDCLAMPF(SrcF[i]); DestPtr[4*x + 3] = 255; } break; case 3: /* Convert RGBA U8 to RGB (or BGR) float */ for(y = 0; y < Height; y++, DestPtr += 4*Width) for(x = 0, i = RowStride*y; x < Width; x++, i += PixelStride) { DestPtr[4*x + Order[0]] = ROUNDCLAMPF(SrcF[i]); DestPtr[4*x + Order[1]] = ROUNDCLAMPF(SrcF[i + ChannelStride]); DestPtr[4*x + Order[2]] = ROUNDCLAMPF(SrcF[i + ChannelStride2]); DestPtr[4*x + 3] = 255; } break; case 4: /* Convert RGBA U8 to RGBA (or BGRA, ARGB, or ABGR) float */ for(y = 0; y < Height; y++, DestPtr += 4*Width) for(x = 0, i = RowStride*y; x < Width; x++, i += PixelStride) { DestPtr[4*x + Order[0]] = ROUNDCLAMPF(SrcF[i]); DestPtr[4*x + Order[1]] = ROUNDCLAMPF(SrcF[i + ChannelStride]); DestPtr[4*x + Order[2]] = ROUNDCLAMPF(SrcF[i + ChannelStride2]); DestPtr[4*x + Order[3]] = ROUNDCLAMPF(SrcF[i + ChannelStride3]); } break; } break; case IMAGEIO_DOUBLE: /* Source type is double */ switch(NumChannels) { case 1: /* Convert grayscale double to RGBA U8 */ for(y = 0; y < Height; y++, DestPtr += 4*Width) for(x = 0, i = RowStride*y; x < Width; x++, i += PixelStride) { DestPtr[4*x] = DestPtr[4*x + 1] = DestPtr[4*x + 2] = ROUNDCLAMP(SrcD[i]); DestPtr[4*x + 3] = 255; } break; case 3: /* Convert RGB (or BGR) double to RGBA U8 */ for(y = 0; y < Height; y++, DestPtr += 4*Width) for(x = 0, i = RowStride*y; x < Width; x++, i += PixelStride) { DestPtr[4*x + Order[0]] = ROUNDCLAMP(SrcD[i]); DestPtr[4*x + Order[1]] = ROUNDCLAMP(SrcD[i + ChannelStride]); DestPtr[4*x + Order[2]] = ROUNDCLAMP(SrcD[i + ChannelStride2]); DestPtr[4*x + 3] = 255;; } break; case 4: /* Convert RGBA (or BGRA, ARGB, or ABGR) double to RGBA U8 */ for(y = 0; y < Height; y++, DestPtr += 4*Width) for(x = 0, i = RowStride*y; x < Width; x++, i += PixelStride) { DestPtr[4*x + Order[0]] = ROUNDCLAMP(SrcD[i]); DestPtr[4*x + Order[1]] = ROUNDCLAMP(SrcD[i + ChannelStride]); DestPtr[4*x + Order[2]] = ROUNDCLAMP(SrcD[i + ChannelStride2]); DestPtr[4*x + Order[3]] = ROUNDCLAMP(SrcD[i + ChannelStride3]); } break; } break; default: return NULL; } return (uint32_t *)Dest; } /** * @brief Identify the file type of an image file by its magic numbers * @param Type destination buffer with space for at least 5 chars * @param FileName image file name * @return 1 on successful identification, 0 on failure. * * The routine fills Type with an identifying string. If there is an error * or the file type is unknown, Type is set to a null string. */ int IdentifyImageType(char *Type, const char *FileName) { FILE *File; uint32_t Magic; Type[0] = '\0'; if(!(File = fopen(FileName, "rb"))) return 0; /* Determine the file format by reading the first 4 bytes */ Magic = ((uint32_t)getc(File)); Magic |= ((uint32_t)getc(File)) << 8; Magic |= ((uint32_t)getc(File)) << 16; Magic |= ((uint32_t)getc(File)) << 24; /* Test for errors */ if(ferror(File)) { fclose(File); return 0; } fclose(File); if((Magic & 0x0000FFFFL) == 0x00004D42L) /* BMP */ strcpy(Type, "BMP"); else if((Magic & 0x00FFFFFFL) == 0x00FFD8FFL) /* JPEG/JFIF */ strcpy(Type, "JPEG"); else if(Magic == 0x474E5089L) /* PNG */ strcpy(Type, "PNG"); else if(Magic == 0x002A4949L || Magic == 0x2A004D4DL) /* TIFF */ strcpy(Type, "TIFF"); else if(Magic == 0x38464947L) /* GIF */ strcpy(Type, "GIF"); else if(Magic == 0x474E4D8AL) /* MNG */ strcpy(Type, "MNG"); else if((Magic & 0xF0FF00FFL) == 0x0001000AL /* PCX */ && ((Magic >> 8) & 0xFF) < 6) strcpy(Type, "PCX"); else return 0; return 1; } /** * @brief Read an image file as 32-bit RGBA data * * @param Width, Height pointers to be filled with the image dimensions * @param FileName image file name * @param Format specifies the desired format for the image * * @return Pointer to the image data, or null on failure * * The calling syntax is that the filename is the input and \c Width, * and \c Height and the returned pointer are outputs. \c ReadImage allocates * memory for the image as one contiguous block of memory and returns a * pointer. It is the responsibility of the caller to call \c Free on this * pointer when done to release this memory. * * A non-null pointer indicates success. On failure, the returned pointer * is null, and \c Width and \c Height are set to 0. * * The Format argument is used by specifying one of the data type options * * - IMAGEIO_U8: unsigned 8-bit components * - IMAGEIO_SINGLE: float components * - IMAGEIO_DOUBLE: double components * * and one of the channel options * * - IMAGEIO_GRAYSCALE: grayscale data * - IMAGEIO_RGB: RGB color data (red is the first channel) * - IMAGEIO_BGR: BGR color data (blue is the first channel) * - IMAGEIO_RGBA: RGBA color+alpha data * - IMAGEIO_BGRA: BGRA color+alpha data * - IMAGEIO_ARGB: ARGB color+alpha data * - IMAGEIO_ABGR: ABGR color+alpha data * * and optionally either or both of the ordering options * * - IMAGEIO_PLANAR: planar order instead of interleaved components * - IMAGEIO_COLUMNMAJOR: column major order instead of row major order * @code uint32_t *Image; int Width, Height; if(!(Image = (uint32_t *)ReadImage(&Width, &Height, "myimage.bmp", IMAGEIO_U8 | IMAGEIO_RGBA))) return 0; printf("Read image of size %dx%d\n", Width, Height); ... Free(Image); @endcode * * With the default formatting IMAGEIO_U8 | IMAGEIO_RGBA, the image is * organized in standard row major top-down 32-bit RGBA order. The image * is organized as @verbatim (Top left) (Top right) Image[0] Image[1] ... Image[Width-1] Image[Width] Image[Width+1] ... Image[2*Width] ... ... ... ... Image[Width*(Height-1)] ... ... Image[Width*Height-1] (Bottom left) (Bottom right) @endverbatim * Each element \c Image[k] represents one RGBA pixel, which is a 32-bit * bitfield. The components of pixel \c Image[k] can be unpacked as @code uint8_t *Component = (uint8_t *)&Image[k]; uint8_t Red = Component[0]; uint8_t Green = Component[1]; uint8_t Blue = Component[2]; uint8_t Alpha = Component[3]; @endcode * Each component is an unsigned 8-bit integer value with range 0-255. Most * images do not have alpha information, in which case the alpha component * is set to value 255 (full opacity). * * With IMAGEIO_SINGLE or IMAGEIO_DOUBLE, the components are values in the * range 0 to 1. */ void *ReadImage(int *Width, int *Height, const char *FileName, unsigned Format) { void *Image = NULL; uint32_t *ImageU8 = NULL; FILE *File; char Type[8]; IdentifyImageType(Type, FileName); if(!(File = fopen(FileName, "rb"))) { ErrorMessage("Unable to open file \"%s\".\n", FileName); return 0; } if(!strcmp(Type, "BMP")) { if(!ReadBmp(&ImageU8, Width, Height, File)) ErrorMessage("Failed to read \"%s\".\n", FileName); } else if(!strcmp(Type, "JPEG")) { #ifdef USE_LIBJPEG if(!(ReadJpeg(&ImageU8, Width, Height, File))) ErrorMessage("Failed to read \"%s\".\n", FileName); #else ErrorMessage("File \"%s\" is a JPEG image.\n" "Compile with USE_LIBJPEG to enable JPEG reading.\n", FileName); #endif } else if(!strcmp(Type, "PNG")) { #ifdef USE_LIBPNG if(!(ReadPng(&ImageU8, Width, Height, File))) ErrorMessage("Failed to read \"%s\".\n", FileName); #else ErrorMessage("File \"%s\" is a PNG image.\n" "Compile with USE_LIBPNG to enable PNG reading.\n", FileName); #endif } else if(!strcmp(Type, "TIFF")) { #ifdef USE_LIBTIFF fclose(File); if(!(ReadTiff(&ImageU8, Width, Height, FileName, 0))) ErrorMessage("Failed to read \"%s\".\n", FileName); File = NULL; #else ErrorMessage("File \"%s\" is a TIFF image.\n" "Compile with USE_LIBTIFF to enable TIFF reading.\n", FileName); #endif } else { /* File format is unsupported. */ if(Type[0]) ErrorMessage("File \"%s\" is a %s image.", FileName, Type); else ErrorMessage("File \"%s\" is an unrecognized format.", FileName); fprintf(stderr, "\nSorry, only " READIMAGE_FORMATS_SUPPORTED " reading is supported.\n"); } if(File) fclose(File); if(ImageU8 && Format) { Image = ConvertToFormat(ImageU8, *Width, *Height, Format); Free(ImageU8); } else Image = ImageU8; return Image; } /** * @brief Write an image file from 8-bit RGBA image data * * @param Image pointer to the image data * @param Width, Height image dimensions * @param FileName image file name * @param Format specifies how the data is formatted (see ReadImage) * @param Quality the JPEG image quality (between 0 and 100) * * @return 1 on success, 0 on failure * * The input \c Image should be a 32-bit RGBA image stored as in the * description of \c ReadImage. \c WriteImage writes to \c FileName in the * file format specified by its extension. If saving a JPEG image, the * \c Quality argument specifies the quality factor (between 0 and 100). * \c Quality has no effect on other formats. * * The return value indicates success with 1 or failure with 0. */ int WriteImage(void *Image, int Width, int Height, const char *FileName, unsigned Format, int Quality) { FILE *File; uint32_t *ImageU8; enum {BMP_FORMAT, JPEG_FORMAT, PNG_FORMAT, TIFF_FORMAT} FileFormat; int Success = 0; if(!Image || Width <= 0 || Height <= 0) { ErrorMessage("Null image.\n"); ErrorMessage("Failed to write \"%s\".\n", FileName); return 0; } if(StringEndsWith(FileName, ".bmp")) FileFormat = BMP_FORMAT; else if(StringEndsWith(FileName, ".jpg") || StringEndsWith(FileName, ".jpeg")) { FileFormat = JPEG_FORMAT; #ifndef USE_LIBJPEG ErrorMessage("Failed to write \"%s\".\n", FileName); ErrorMessage("Compile with USE_LIBJPEG to enable JPEG writing.\n"); return 0; #endif } else if(StringEndsWith(FileName, ".png")) { FileFormat = PNG_FORMAT; #ifndef USE_LIBPNG ErrorMessage("Failed to write \"%s\".\n", FileName); ErrorMessage("Compile with USE_LIBPNG to enable PNG writing.\n"); return 0; #endif } else if(StringEndsWith(FileName, ".tif") || StringEndsWith(FileName, ".tiff")) { FileFormat = TIFF_FORMAT; #ifndef USE_LIBTIFF ErrorMessage("Failed to write \"%s\".\n", FileName); ErrorMessage("Compile with USE_LIBTIFF to enable TIFF writing.\n"); return 0; #endif } else { ErrorMessage("Failed to write \"%s\".\n", FileName); if(StringEndsWith(FileName, ".gif")) ErrorMessage("GIF is not supported. "); else if(StringEndsWith(FileName, ".mng")) ErrorMessage("MNG is not supported. "); else if(StringEndsWith(FileName, ".pcx")) ErrorMessage("PCX is not supported. "); else ErrorMessage("Unable to determine format from extension.\n"); ErrorMessage("Sorry, only " WRITEIMAGE_FORMATS_SUPPORTED " writing is supported.\n"); return 0; } if(!(File = fopen(FileName, "wb"))) { ErrorMessage("Unable to write to file \"%s\".\n", FileName); return 0; } if(!(ImageU8 = ConvertFromFormat(Image, Width, Height, Format))) return 0; switch(FileFormat) { case BMP_FORMAT: Success = WriteBmp(ImageU8, Width, Height, File); break; case JPEG_FORMAT: #ifdef USE_LIBJPEG Success = WriteJpeg(ImageU8, Width, Height, File, Quality); #else /* Dummy operation to avoid unused variable warning if compiled without libjpeg. Note that execution returns above if Format == JPEG_FORMAT and USE_LIBJPEG is undefined. */ Success = Quality; #endif break; case PNG_FORMAT: #ifdef USE_LIBPNG Success = WritePng(ImageU8, Width, Height, File); #endif break; case TIFF_FORMAT: #ifdef USE_LIBTIFF fclose(File); Success = WriteTiff(ImageU8, Width, Height, FileName); File = 0; #endif break; } if(!Success) ErrorMessage("Failed to write \"%s\".\n", FileName); Free(ImageU8); if(File) fclose(File); return Success; }