diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..a59d834 --- /dev/null +++ b/LICENSE @@ -0,0 +1,27 @@ +Simplified BSD License +http://www.opensource.org/licenses/bsd-license.html + +Copyright (c) 2010-2011, Pascal Getreuer +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * 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. + +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. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..88ef977 --- /dev/null +++ b/Makefile @@ -0,0 +1,77 @@ +# GCC makefile for Ipol.im library image input-output +# Pascal Getreuer +# May 4, 2012 + +LIBIPOLN=libipoliio.so +LIBIPOLV=0 +LIBIPOLD=20130706 +## +# The following three statements determine the build configuration. +# For handling different image formats, the program can be linked with +# the libjpeg, libpng, and libtiff libraries. For each library, set +# the flags needed for linking. To disable use of a library, comment +# its statement. You can disable all three (BMP is always supported). +LDLIBJPEG=-ljpeg +LDLIBPNG=-lpng +LDLIBTIFF=-ltiff + +## +# Standard make settings +SHELL=/bin/sh +CFLAGS=-O3 -ansi -pedantic -Wall -Wextra +LDFLAGS= +LDLIBS=-lm $(LDLIBJPEG) $(LDLIBPNG) $(LDLIBTIFF) + +## +# These statements add compiler flags to define USE_LIBJPEG, etc., +# depending on which libraries have been specified above. +ifneq ($(LDLIBJPEG),) + CJPEG=-DUSE_LIBJPEG +endif +ifneq ($(LDLIBPNG),) + CPNG=-DUSE_LIBPNG +endif +ifneq ($(LDLIBTIFF),) + CTIFF=-DUSE_LIBTIFF +endif + +ALLCFLAGS=$(CFLAGS) $(CJPEG) $(CPNG) $(CTIFF) + +LIBIPOL_SOURCES=basic.c imageio.c + +ARCHIVENAME=$(LIBIPOLN)_$(shell date -u +%Y%m%d) +SOURCES=basic.c basic.h imageio.c imageio.h LICENSE Makefile +LIBIPOL_OBJECTS=$(LIBIPOL_SOURCES:.c=.o) +.SUFFIXES: .c .o +.PHONY: all clean rebuild dist dist-zip + +all: $(LIBIPOLN).$(LIBIPOLV) + +$(LIBIPOLN).$(LIBIPOLV): $(LIBIPOL_OBJECTS) + $(CC) -shared -Wl,-soname,$@ $(LDFLAGS) -o $@ $^ $(LDLIBS) -s + chmod -v 644 $@ + mv $@ $@.$(LIBIPOLD) + ln -s $@.$(LIBIPOLD) $@ + ln -s $@ $(LIBIPOLN) + +.c.o: + $(CC) -c $(ALLCFLAGS) $< -o $@ + +clean: + -$(RM) $(LIBIPOL_OBJECTS) $(LIBIPOLN)* + +rebuild: clean all +dist: $(SOURCES) + -rm -rf $(ARCHIVENAME) + mkdir $(ARCHIVENAME) + ln $(SOURCES) $(ARCHIVENAME) + tar vchzf $(ARCHIVENAME).tgz $(ARCHIVENAME) + -rm -rf $(ARCHIVENAME) + +dist-zip: $(SOURCES) + -rm -rf $(ARCHIVENAME) + mkdir $(ARCHIVENAME) + ln $(SOURCES) $(ARCHIVENAME) + -rm -f $(ARCHIVENAME).zip + zip -r9 $(ARCHIVENAME).zip $(ARCHIVENAME)/* + -rm -rf $(ARCHIVENAME) diff --git a/README.md b/README.md new file mode 100644 index 0000000..c885606 --- /dev/null +++ b/README.md @@ -0,0 +1,40 @@ +% IPOL.IM Library Image Input-Output. + +# ABOUT + +* Author : Pascal Getreuer +* Copyright : (C) 2010-2012 IPOL Image Processing On Line http://www.ipol.im/ +* Licence : BSD +* Latest version available at: https://github.com/zvezdochiot/libipoliio + +# OVERVIEW + +This source code provides an implementation of the "Image Input-Output" +described in the IPOL article: http://www.ipol.im/ + +# UNIX/LINUX/MAC USER GUIDE + +The code is compilable on Unix/Linux and Mac OS. + +- Compilation. +Automated compilation requires the make. + +- Dependencies. +This code requires the libpng, libtiff, libjpeg. + +- Image formats. +Only the BMP, PNG, JPEG, and TIFF (float) formats are supported. + +------------------------------------------------------------------------- +Usage: +1. Download the code package and extract it. Go to that directory. + +2. Compile the source code (on Unix/Linux/Mac OS). + + make; + +# ABOUT THIS FILE +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 . diff --git a/VERSION b/VERSION new file mode 100644 index 0000000..1b8c396 --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +0.20130706 diff --git a/basic.c b/basic.c new file mode 100644 index 0000000..a14d89c --- /dev/null +++ b/basic.c @@ -0,0 +1,125 @@ +/** + * @file basic.c + * @brief Memory management, portable types, math constants, and timing + * @author Pascal Getreuer + * + * This file implements a function Clock, a timer with millisecond + * precision. In order to obtain timing at high resolution, platform- + * specific functions are needed: + * + * - On Windows systems, the GetSystemTime function is used. + * - On POSIX systems, the gettimeofday function is used. + * + * Otherwise as a fallback, time.h time is used, and in this case Clock has + * only second accuracy. This file attempts to detect whether the platform + * is POSIX or Windows and defines Clock accordingly. A particular + * implementation can be forced by defining USE_GETSYSTEMTIME, + * USE_GETTIMEOFDAY, or USE_TIME. + * + * + * Copyright (c) 2010-2011, 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 "basic.h" + + +/* Autodetect whether to use Windows, POSIX, + or fallback implementation for Clock. */ +#if !defined(USE_GETSYSTEMTIME) && !defined(USE_GETTIMEOFDAY) && !defined(USE_TIME) +# if defined(WIN32) || defined(_WIN32) || defined(WIN64) || defined(_WIN64) +# define USE_GETSYSTEMTIME +# elif defined(unix) || defined(__unix__) || defined(__unix) +# include +# if (_POSIX_TIMERS) || (_POSIX_VERSION >= 200112L) +# define USE_GETTIMEOFDAY +# endif +# endif +#endif + +/* Define Clock(), get the system clock in milliseconds */ +#if defined(USE_GETSYSTEMTIME) +#define WIN32_LEAN_AND_MEAN +#include + +unsigned long Clock() /* Windows implementation */ +{ + static SYSTEMTIME TimeVal; + GetSystemTime(&TimeVal); + return (unsigned long)((unsigned long)TimeVal.wMilliseconds + + 1000*((unsigned long)TimeVal.wSecond + + 60*((unsigned long)TimeVal.wMinute + + 60*((unsigned long)TimeVal.wHour + + 24*(unsigned long)TimeVal.wDay)))); +} +#elif defined(USE_GETTIMEOFDAY) +#include +#include + +unsigned long Clock() /* POSIX implementation */ +{ + struct timeval TimeVal; + gettimeofday(&TimeVal, NULL); + return (unsigned long)(TimeVal.tv_usec/1000 + TimeVal.tv_sec*1000); +} +#else +#include + +unsigned long Clock() /* Fallback implementation */ +{ + time_t RawTime; + struct tm *TimeVal; + time(&RawTime); + TimeVal = localtime(&RawTime); + return (unsigned long)(1000*((unsigned long)TimeVal->tm_sec + + 60*((unsigned long)TimeVal->tm_min + + 60*((unsigned long)TimeVal->tm_hour + + 24*(unsigned long)TimeVal->tm_mday)))); +} +#endif + + +/** @brief malloc with an error message on failure. */ +void *MallocWithErrorMessage(size_t Size) +{ + void *Ptr; + + if(!(Ptr = malloc(Size))) + ErrorMessage("Memory allocation of %u bytes failed.\n", Size); + + return Ptr; +} + + +/** @brief realloc with an error message and free on failure. */ +void *ReallocWithErrorMessage(void *Ptr, size_t Size) +{ + void *NewPtr; + + if(!(NewPtr = realloc(Ptr, Size))) + { + ErrorMessage("Memory reallocation of %u bytes failed.\n", Size); + Free(Ptr); /* Free the previous block on failure */ + } + + return NewPtr; +} + + +/** @brief Redefine this function to customize error messages. */ +void ErrorMessage(const char *Format, ...) +{ + va_list Args; + + va_start(Args, Format); + /* Write a formatted error message to stderr */ + vfprintf(stderr, Format, Args); + va_end(Args); +} diff --git a/basic.h b/basic.h new file mode 100644 index 0000000..7195209 --- /dev/null +++ b/basic.h @@ -0,0 +1,179 @@ +/** + * @file basic.h + * @brief Memory management, portable types, math constants, and timing + * @author Pascal Getreuer + * + * This purpose of this file is to improve portability. + * + * Types \c uint8_t, \c uint16_t, \c uint32_t should be defined as + * unsigned integer types such that + * @li \c uint8_t is 8-bit, range 0 to 255 + * @li \c uint16_t is 16-bit, range 0 to 65535 + * @li \c uint32_t is 32-bit, range 0 to 4294967295 + * + * Similarly, \c int8_t, \c int16_t, \c int32_t should be defined as + * signed integer types such that + * @li \c int8_t is 8-bit, range -128 to +127 + * @li \c int16_t is 16-bit, range -32768 to +32767 + * @li \c int32_t is 32-bit, range -2147483648 to +2147483647 + * + * These definitions are implemented with types \c __int8, \c __int16, + * and \c __int32 under Windows and by including stdint.h under UNIX. + * + * To define the math constants, math.h is included, and any of the + * following that were not defined by math.h are defined here according + * to the values from Hart & Cheney. + * @li M_2PI = 2 pi = 6.28318530717958647692528676655900576 + * @li M_PI = pi = 3.14159265358979323846264338327950288 + * @li M_PI_2 = pi/2 = 1.57079632679489661923132169163975144 + * @li M_PI_4 = pi/4 = 0.78539816339744830961566084581987572 + * @li M_PI_8 = pi/8 = 0.39269908169872415480783042290993786 + * @li M_SQRT2 = sqrt(2) = 1.41421356237309504880168872420969808 + * @li M_1_SQRT2 = 1/sqrt(2) = 0.70710678118654752440084436210484904 + * @li M_E = e = 2.71828182845904523536028747135266250 + * @li M_LOG2E = log_2(e) = 1.44269504088896340735992468100189213 + * @li M_LOG10E = log_10(e) = 0.43429448190325182765112891891660508 + * @li M_LN2 = log_e(2) = 0.69314718055994530941723212145817657 + * @li M_LN10 = log_e(10) = 2.30258509299404568401799145468436421 + * @li M_EULER = Euler = 0.57721566490153286060651209008240243 + * @li M_SQRT2PI = sqrt(2 pi) = 2.50662827463100050241576528481104525 + * + * + * Copyright (c) 2010-2011, 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 . + */ + +#ifndef _BASIC_H_ +#define _BASIC_H_ + +#include +#include +#include + + +/* Memory management */ +/** @brief Function to allocate a block of memory */ +#define Malloc(s) MallocWithErrorMessage(s) +void *MallocWithErrorMessage(size_t Size); +/** @brief Function to reallocate a block of memory */ +#define Realloc(p, s) ReallocWithErrorMessage(p, s) +void *ReallocWithErrorMessage(void *Ptr, size_t Size); +/** @brief Function to free memory */ +#define Free(p) free(p) + + +/* Portable integer types */ +#if defined(WIN32) || defined(_WIN32) || defined(WIN64) || defined(_WIN64) + + /* Windows system: Use __intN types to define uint8_t, etc. */ + typedef unsigned __int8 uint8_t; + typedef unsigned __int16 uint16_t; + typedef unsigned __int32 uint32_t; + typedef __int8 int8_t; + typedef __int16 int16_t; + typedef __int32 int32_t; + +#else + + /* UNIX system: Use stdint to define uint8_t, etc. */ + #include + +#endif + + +/* Math constants (Hart & Cheney) */ +#ifndef M_2PI +/** @brief The constant 2 pi */ +#define M_2PI 6.28318530717958647692528676655900576 +#endif +#ifndef M_PI +/** @brief The constant pi */ +#define M_PI 3.14159265358979323846264338327950288 +#endif +#ifndef M_PI_2 +/** @brief The constant pi/2 */ +#define M_PI_2 1.57079632679489661923132169163975144 +#endif +#ifndef M_PI_4 +/** @brief The constant pi/4 */ +#define M_PI_4 0.78539816339744830961566084581987572 +#endif +#ifndef M_PI_8 +/** @brief The constant pi/8 */ +#define M_PI_8 0.39269908169872415480783042290993786 +#endif +#ifndef M_SQRT2 +/** @brief The constant sqrt(2) */ +#define M_SQRT2 1.41421356237309504880168872420969808 +#endif +#ifndef M_1_SQRT2 +/** @brief The constant 1/sqrt(2) */ +#define M_1_SQRT2 0.70710678118654752440084436210484904 +#endif +#ifndef M_E +/** @brief The natural number */ +#define M_E 2.71828182845904523536028747135266250 +#endif +#ifndef M_LOG2E +/** @brief Log base 2 of the natural number */ +#define M_LOG2E 1.44269504088896340735992468100189213 +#endif +#ifndef M_LOG10E +/** @brief Log base 10 of the natural number */ +#define M_LOG10E 0.43429448190325182765112891891660508 +#endif +#ifndef M_LN2 +/** @brief Natural log of 2 */ +#define M_LN2 0.69314718055994530941723212145817657 +#endif +#ifndef M_LN10 +/** @brief Natural log of 10 */ +#define M_LN10 2.30258509299404568401799145468436421 +#endif +#ifndef M_EULER +/** @brief Euler number */ +#define M_EULER 0.57721566490153286060651209008240243 +#endif + +#ifndef M_SQRT2PI +/** + * @brief The constant sqrt(2 pi) + * Verified with Maxima using 80-digit precision + */ +#define M_SQRT2PI 2.50662827463100050241576528481104525 +#endif + +/** @brief Round double X */ +#define ROUND(X) (floor((X) + 0.5)) + +/** @brief Round float X */ +#define ROUNDF(X) (floor((X) + 0.5f)) + + +#ifdef __GNUC__ + #ifndef ATTRIBUTE_UNUSED + /** @brief Macro for the unused attribue GNU extension */ + #define ATTRIBUTE_UNUSED __attribute__((unused)) + #endif + #ifndef ATTRIBUTE_ALWAYSINLINE + /** @brief Macro for the always inline attribue GNU extension */ + #define ATTRIBUTE_ALWAYSINLINE __attribute__((always_inline)) + #endif +#else + #define ATTRIBUTE_UNUSED + #define ATTRIBUTE_ALWAYSINLINE +#endif + + +/* Error messaging */ +void ErrorMessage(const char *Format, ...); + +/* Timer function */ +unsigned long Clock(); + +#endif /* _BASIC_H_ */ diff --git a/imageio.c b/imageio.c new file mode 100644 index 0000000..13384cc --- /dev/null +++ b/imageio.c @@ -0,0 +1,2186 @@ +/** + * @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; +} diff --git a/imageio.h b/imageio.h new file mode 100644 index 0000000..f21fdaa --- /dev/null +++ b/imageio.h @@ -0,0 +1,95 @@ +/** + * @file imageio.h + * @brief Implements ReadImage and WriteImage functions + * @author Pascal Getreuer + * + * + * 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 . + */ + +#ifndef _IMAGEIO_H_ +#define _IMAGEIO_H_ + +#include +#include "basic.h" + +/** @brief Limit on the maximum allowed image width or height (security). */ +#define MAX_IMAGE_SIZE 10000 + + +#ifndef DOXYGEN_SHOULD_SKIP_THIS + +/* Build string macros listing the supported formats */ +#ifdef USE_LIBJPEG +#define SUPPORTEDSTRING_JPEG "/JPEG" +#else +#define SUPPORTEDSTRING_JPEG "" +#endif +#ifdef USE_LIBPNG +#define SUPPORTEDSTRING_PNG "/PNG" +#else +#define SUPPORTEDSTRING_PNG "" +#endif +#ifdef USE_LIBTIFF +#define SUPPORTEDSTRING_TIFF "/TIFF" +#else +#define SUPPORTEDSTRING_TIFF "" +#endif + +/* Definitions for specifying image formats */ +#define IMAGEIO_U8 0x0000 +#define IMAGEIO_SINGLE 0x0001 +#define IMAGEIO_FLOAT IMAGEIO_SINGLE +#define IMAGEIO_DOUBLE 0x0002 +#define IMAGEIO_STRIP_ALPHA 0x0010 +#define IMAGEIO_BGRFLIP 0x0020 +#define IMAGEIO_AFLIP 0x0040 +#define IMAGEIO_GRAYSCALE 0x0080 +#define IMAGEIO_GRAY IMAGEIO_GRAYSCALE +#define IMAGEIO_PLANAR 0x0100 +#define IMAGEIO_COLUMNMAJOR 0x0200 +#define IMAGEIO_RGB (IMAGEIO_STRIP_ALPHA) +#define IMAGEIO_BGR (IMAGEIO_STRIP_ALPHA | IMAGEIO_BGRFLIP) +#define IMAGEIO_RGBA 0x0000 +#define IMAGEIO_BGRA (IMAGEIO_BGRFLIP) +#define IMAGEIO_ARGB (IMAGEIO_AFLIP) +#define IMAGEIO_ABGR (IMAGEIO_BGRFLIP | IMAGEIO_AFLIP) + +#endif /* DOXYGEN_SHOULD_SKIP_THIS */ + + +/** + * @brief String macro listing supported formats for \c ReadImage + * + * This macro can be used for example as +@code + printf("Supported formats for reading: " READIMAGE_FORMATS_SUPPORTED ".\n"); +@endcode + */ +#define READIMAGE_FORMATS_SUPPORTED \ + "BMP" SUPPORTEDSTRING_JPEG SUPPORTEDSTRING_PNG SUPPORTEDSTRING_TIFF + +/** @brief String macro listing supported formats for \c WriteImage */ +#define WRITEIMAGE_FORMATS_SUPPORTED \ + "BMP" SUPPORTEDSTRING_JPEG SUPPORTEDSTRING_PNG SUPPORTEDSTRING_TIFF + +#ifndef _CRT_SECURE_NO_WARNINGS +/** @brief Avoid MSVC warnings on using fopen */ +#define _CRT_SECURE_NO_WARNINGS +#endif + +int IdentifyImageType(char *Type, const char *FileName); + +void *ReadImage(int *Width, int *Height, + const char *FileName, unsigned Format); + +int WriteImage(void *Image, int Width, int Height, + const char *FileName, unsigned Format, int Quality); + +#endif /* _IMAGEIO_H_ */