/* vim:expandtab:ts=2 sw=2: */ /* Grafx2 - The Ultimate 256-color bitmap paint program Copyright owned by various GrafX2 authors, see COPYRIGHT.txt for details. Grafx2 is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; version 2 of the License. Grafx2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Grafx2; if not, see */ ///@file fileformats.c /// Saving and loading different picture formats. #include #ifndef __no_pnglib__ // just for png_sig_cmp() #include #endif #include #if defined(_MSC_VER) #include #if _MSC_VER < 1900 #define snprintf _snprintf #endif #endif #if !defined(WIN32) && !defined(USE_SDL) && !defined(USE_SDL2) #if defined(__macosx__) #include #elif defined(__FreeBSD__) #include #else #include #endif #endif #include "gfx2log.h" #include "errors.h" #include "global.h" #include "loadsave.h" #include "loadsavefuncs.h" #include "struct.h" #include "io.h" #include "pages.h" #include "windows.h" // Best_color() #include "unicode.h" #include "fileformats.h" #include "oldies.h" #include "bitcount.h" #ifndef MIN #define MIN(a,b) ((a)<(b)?(a):(b)) #endif void Draw_IFF_line(T_IO_Context *context, const byte * buffer, short y_pos, short real_line_size, byte bitplanes); //////////////////////////////////// IMG //////////////////////////////////// // -- Tester si un fichier est au format IMG -------------------------------- void Test_IMG(T_IO_Context * context, FILE * file) { T_IMG_Header IMG_header; static const byte signature[6]={0x01,0x00,0x47,0x12,0x6D,0xB0}; (void)context; File_error=1; // Lecture et vérification de la signature if (Read_bytes(file,IMG_header.Filler1,6) && Read_word_le(file,&(IMG_header.Width)) && Read_word_le(file,&(IMG_header.Height)) && Read_bytes(file,IMG_header.Filler2,118) && Read_bytes(file,IMG_header.Palette,sizeof(T_Palette)) ) { if ( (!memcmp(IMG_header.Filler1,signature,6)) && IMG_header.Width && IMG_header.Height) File_error=0; } } // -- Lire un fichier au format IMG ----------------------------------------- void Load_IMG(T_IO_Context * context) { byte * buffer; FILE *file; word x_pos,y_pos; long file_size; T_IMG_Header IMG_header; File_error=0; if ((file=Open_file_read(context))) { file_size=File_length_file(file); if (Read_bytes(file,IMG_header.Filler1,6) && Read_word_le(file,&(IMG_header.Width)) && Read_word_le(file,&(IMG_header.Height)) && Read_bytes(file,IMG_header.Filler2,118) && Read_bytes(file,IMG_header.Palette,sizeof(T_Palette)) ) { buffer=(byte *)malloc(IMG_header.Width); Pre_load(context, IMG_header.Width,IMG_header.Height,file_size,FORMAT_IMG,PIXEL_SIMPLE,0); if (File_error==0) { memcpy(context->Palette,IMG_header.Palette,sizeof(T_Palette)); for (y_pos=0;(y_posHeight) && (!File_error);y_pos++) { if (Read_bytes(file,buffer,context->Width)) { for (x_pos=0; x_posWidth;x_pos++) Set_pixel(context, x_pos,y_pos,buffer[x_pos]); } else File_error=2; } } free(buffer); buffer = NULL; } else File_error=1; fclose(file); } else File_error=1; } // -- Sauver un fichier au format IMG --------------------------------------- void Save_IMG(T_IO_Context * context) { FILE *file; short x_pos,y_pos; T_IMG_Header IMG_header; byte signature[6]={0x01,0x00,0x47,0x12,0x6D,0xB0}; File_error=0; // Ouverture du fichier if ((file=Open_file_write(context))) { setvbuf(file, NULL, _IOFBF, 64*1024); memcpy(IMG_header.Filler1,signature,6); IMG_header.Width=context->Width; IMG_header.Height=context->Height; memset(IMG_header.Filler2,0,118); IMG_header.Filler2[4]=0xFF; IMG_header.Filler2[22]=64; // Lo(Longueur de la signature) IMG_header.Filler2[23]=0; // Hi(Longueur de la signature) memcpy(IMG_header.Filler2+23,"GRAFX2 by SunsetDesign (IMG format taken from PV (c)W.Wiedmann)",64); memcpy(IMG_header.Palette,context->Palette,sizeof(T_Palette)); if (Write_bytes(file,IMG_header.Filler1,6) && Write_word_le(file,IMG_header.Width) && Write_word_le(file,IMG_header.Height) && Write_bytes(file,IMG_header.Filler2,118) && Write_bytes(file,IMG_header.Palette,sizeof(T_Palette)) ) { for (y_pos=0; ((y_posHeight) && (!File_error)); y_pos++) for (x_pos=0; x_posWidth; x_pos++) Write_one_byte(file,Get_pixel(context, x_pos,y_pos)); fclose(file); if (File_error) Remove_file(context); } else // Error d'écriture (disque plein ou protégé) { fclose(file); Remove_file(context); File_error=1; } } else { File_error=1; } } /////////////////////////// .info (Amiga Icons) ///////////////////////////// typedef struct { // offset word Magic; // 0 word Version; dword NextGadget; word LeftEdge; // 8 word TopEdge; word Width; word Height; word Flags; // 16 word Activation; word GadgetType; // 20 dword GadgetRender; // 22 dword SelectRender; dword GadgetText; // 30 dword MutualExclude; dword SpecialInfo; // 38 word GadgetID; dword UserData; // 44 icon revision : 0 = OS 1.x, 1 = OS 2.x/3.x byte Type; byte padding; dword DefaultTool; dword ToolTypes; dword CurrentX; dword CurrentY; dword DrawerData; dword ToolWindow; dword StackSize; } T_INFO_Header; static int Read_INFO_Header(FILE * file, T_INFO_Header * header) { return (Read_word_be(file, &header->Magic) && Read_word_be(file, &header->Version) && Read_dword_be(file, &header->NextGadget) && Read_word_be(file, &header->LeftEdge) && Read_word_be(file, &header->TopEdge) && Read_word_be(file, &header->Width) && Read_word_be(file, &header->Height) && Read_word_be(file, &header->Flags) && Read_word_be(file, &header->Activation) && Read_word_be(file, &header->GadgetType) && Read_dword_be(file, &header->GadgetRender) && Read_dword_be(file, &header->SelectRender) && Read_dword_be(file, &header->GadgetText) && Read_dword_be(file, &header->MutualExclude) && Read_dword_be(file, &header->SpecialInfo) && Read_word_be(file, &header->GadgetID) && Read_dword_be(file, &header->UserData) && Read_byte(file, &header->Type) && Read_byte(file, &header->padding) && Read_dword_be(file, &header->DefaultTool) && Read_dword_be(file, &header->ToolTypes) && Read_dword_be(file, &header->CurrentX) && Read_dword_be(file, &header->CurrentY) && Read_dword_be(file, &header->DrawerData) && Read_dword_be(file, &header->ToolWindow) && Read_dword_be(file, &header->StackSize) ); } typedef struct { short LeftEdge; short TopEdge; word Width; word Height; word Depth; dword ImageData; byte PlanePick; byte PlaneOnOff; dword NextImage; } T_INFO_ImageHeader; static int Read_INFO_ImageHeader(FILE * file, T_INFO_ImageHeader * header) { return (Read_word_be(file, (word *)&header->LeftEdge) && Read_word_be(file, (word *)&header->TopEdge) && Read_word_be(file, &header->Width) && Read_word_be(file, &header->Height) && Read_word_be(file, &header->Depth) && Read_dword_be(file, &header->ImageData) && Read_byte(file, &header->PlanePick) && Read_byte(file, &header->PlaneOnOff) && Read_dword_be(file, &header->NextImage) ); } void Test_INFO(T_IO_Context * context, FILE * file) { T_INFO_Header header; (void)context; File_error=1; if (Read_INFO_Header(file, &header)) { if (header.Magic == 0xe310 && header.Version == 1) File_error = 0; } } static char * Read_INFO_String(FILE * file) { dword size; char * p; if (!Read_dword_be(file, &size)) return NULL; p = malloc(size); if (p == NULL) return NULL; if (!Read_bytes(file, p, size)) { free(p); p = NULL; } return p; } static byte * Decode_NewIcons(const byte * p, int bits, unsigned int * len) { int alloc_size; unsigned int i; byte * buffer; dword current_byte = 0; int current_bits = 0; alloc_size = 16*1024; buffer = malloc(alloc_size); if (buffer == NULL) return NULL; i = 0; while (*p) { byte value = *p++; if (0xd1 <= value) { value -= 0xd0; // RLE count while (value-- > 0) { current_byte = (current_byte << 7); current_bits += 7; while (current_bits >= bits) { buffer[i++] = (current_byte >> (current_bits - bits)) & ((1 << bits) - 1); current_bits -= bits; } } continue; } if (0x20 <= value && value <= 0x6f) value = value - 0x20; else if (0xa1 <= value && value <= 0xd0) value = value - 0x51; else { GFX2_Log(GFX2_WARNING, "Decode_NewIcons(): Invalid value 0x%02x\n", value); break; } current_byte = (current_byte << 7) | value; current_bits += 7; while (current_bits >= bits) { buffer[i++] = (current_byte >> (current_bits - bits)) & ((1 << bits) - 1); current_bits -= bits; } } //buffer[i++] = (current_byte << (bits - current_bits)) & ((1 << bits) - 1); *len = i; return buffer; } void Load_INFO(T_IO_Context * context) { static const T_Components amigaOS1x_pal[] = { // { 85, 170, 255 }, { 0, 85, 170 }, { 255, 255, 255 }, { 0, 0, 0 }, { 255, 136, 0 }, }; static const T_Components amigaOS2x_pal[] = { { 149, 149, 149 }, { 0, 0, 0 }, { 255, 255, 255 }, { 59, 103, 162 }, { 123, 123, 123 }, // MagicWB extended colors { 175, 175, 175 }, { 170, 144, 124 }, { 255, 169, 151 }, { 255, 0, 0 }, // { 160, 0, 0 }, // { 0, 246, 255 }, // { 0, 0, 255 }, // { 0, 160, 0 }, // { 0, 255, 0 }, // { 255, 255, 0 }, // { 255, 160, 0 }, // }; T_INFO_Header header; T_INFO_ImageHeader imgheaders[2]; FILE *file; long file_size; word plane_line_size = 0; word line_size = 0; int plane; short x_pos = 0, y_pos = 0; int has_NewIcons = 0; int img_count = 0; // 1 or 2 byte * buffers[2]; File_error = 0; file = Open_file_read(context); if (file == NULL) { File_error=1; return; } file_size = File_length_file(file); if (Read_INFO_Header(file, &header) && header.Magic == 0xe310) { if (header.GadgetRender == 0) File_error = 1; if (header.DrawerData) // Skip DrawerData if (fseek(file, 56, SEEK_CUR) != 0) File_error = 1; // icons for (img_count = 0; File_error == 0 && img_count < 2; img_count++) { buffers[img_count] = NULL; if (img_count == 0 && header.GadgetRender == 0) continue; if (img_count == 1 && header.SelectRender == 0) break; if (!Read_INFO_ImageHeader(file, &imgheaders[img_count])) File_error = 1; else { plane_line_size = ((imgheaders[img_count].Width + 15) >> 4) * 2; line_size = plane_line_size * imgheaders[img_count].Depth; buffers[img_count] = malloc(line_size * imgheaders[img_count].Height); for (plane = 0; plane < imgheaders[img_count].Depth; plane++) { for (y_pos = 0; y_pos < imgheaders[img_count].Height; y_pos++) { if (!Read_bytes(file, buffers[img_count] + y_pos * line_size + plane * plane_line_size, plane_line_size)) { File_error = 2; break; } } } } } if (File_error == 0 && header.DefaultTool) { char * DefaultTool = Read_INFO_String(file); if (DefaultTool == NULL) File_error = 2; else { free(DefaultTool); } } if (File_error == 0 && header.ToolTypes) { int i; int current_img = -1; int width = 0; int height = 0; int palette_continues = 0; unsigned int color_count = 0; unsigned int palette_color_count = 0; // used colors in global palette int bpp = 0; byte palette_conv[256]; // to translate sub icon specific palette to global palette dword count; char * ToolType; if (Read_dword_be(file, &count)) { int look_for_comments = 1; for (i = 0; i < 256; i++) palette_conv[i] = (byte)i; while(count > 0) { ToolType = Read_INFO_String(file); if (ToolType == NULL) break; else { // NewIcons if (strlen(ToolType) > 4 && ToolType[0] == 'I' && ToolType[1] == 'M' && ToolType[3] == '=') { const byte * p; int img_index = ToolType[2] - '0'; p = (byte *)ToolType + 4; if (img_index != current_img) { // image info + palette T_Components * palette; unsigned int palette_len; current_img = img_index; if (current_img > 1 && (context->Type == CONTEXT_PREVIEW || context->Type == CONTEXT_PREVIEW_PALETTE)) break; // don't load 2nd image in preview mode if (*p++ == 'B') { context->Transparent_color = 0; context->Background_transparent = 1; } else context->Background_transparent = 0; width = *p++ - 0x21; height = *p++ - 0x21; color_count = *p++ - 0x21; color_count = (color_count << 6) + *p++ - 0x21; for (bpp = 1; color_count > (unsigned)(1 << bpp); bpp++) { } palette = (T_Components *)Decode_NewIcons(p, 8, &palette_len); if (palette) { if (img_index == 1) { // Set palette if (Config.Clear_palette) memset(context->Palette,0,sizeof(T_Palette)); memcpy(context->Palette, palette, MIN(palette_len,sizeof(T_Palette))); if (palette_len < color_count * 3) { palette_color_count = palette_len / 3; palette_continues = 1; } else { palette_color_count = color_count; palette_continues = 0; } } else { // merge palette with the existing one for (i = 0; (unsigned)i < MIN(color_count,palette_len/3); i++) { unsigned int j; for (j = 0; j < palette_color_count; j++) { if (0 == memcmp(context->Palette + j, palette + i, sizeof(T_Components))) break; } if (j == palette_color_count) { if (palette_color_count < 256) { // Add color to palette memcpy(context->Palette + palette_color_count, palette + i, sizeof(T_Components)); palette_color_count++; } else GFX2_Log(GFX2_WARNING, "Too much colors in new icons\n"); } palette_conv[i] = (byte)j; } if (palette_len < color_count * 3) palette_continues = palette_len / 3; else palette_continues = 0; } free(palette); } if (img_index == 1) { Pre_load(context, width, height,file_size,FORMAT_INFO,PIXEL_SIMPLE, bpp); Set_image_mode(context, IMAGE_MODE_ANIMATION); has_NewIcons = 1; } else Set_loading_layer(context, img_index - 1); x_pos = 0; y_pos = 0; } else if (palette_continues) { T_Components * palette; unsigned int palette_len; palette = (T_Components *)Decode_NewIcons(p, 8, &palette_len); if (palette) { if (img_index == 1) { memcpy(context->Palette + palette_color_count, palette, MIN(palette_len,sizeof(T_Palette) - 3*palette_color_count)); if (palette_color_count * 3 + palette_len < color_count * 3) palette_color_count += palette_len / 3; else { palette_color_count = color_count; palette_continues = 0; } } else { // merge palette with the existing one for (i = 0; (unsigned)i < MIN(color_count-palette_continues,palette_len/3); i++) { unsigned int j; for (j = 0; j < palette_color_count; j++) { if (0 == memcmp(context->Palette + j, palette + i, sizeof(T_Components))) break; } if (j == palette_color_count) { if (palette_color_count < 256) { // Add color to palette memcpy(context->Palette + palette_color_count, palette + i, sizeof(T_Components)); palette_color_count++; } else GFX2_Log(GFX2_WARNING, "Too much colors in new icons\n"); } palette_conv[i+palette_continues] = (byte)j; } if (palette_continues * 3 + palette_len < color_count * 3) palette_continues += palette_len / 3; else palette_continues = 0; } free(palette); } } else { byte * pixels; unsigned int pixel_count; pixels = Decode_NewIcons(p, bpp, &pixel_count); if (pixels) { for (i = 0; (unsigned)i < pixel_count; i++) { Set_pixel(context, x_pos++, y_pos, palette_conv[pixels[i]]); if (x_pos >= width) { x_pos = 0; y_pos++; } } free(pixels); } } } else if (look_for_comments) { if (strlen(ToolType) > 1) { if (strcmp(ToolType, "*** DON'T EDIT THE FOLLOWING LINES!! ***") == 0) { // The following lines are NewIcons data! look_for_comments = 0; } else if (context->Comment[0] == '\0') { strncpy(context->Comment, ToolType, COMMENT_SIZE); context->Comment[COMMENT_SIZE] = '\0'; } } } free(ToolType); } count -= 4; } } } if (File_error == 0 && header.ToolWindow != 0) { char * ToolWindow = Read_INFO_String(file); if (ToolWindow == NULL) File_error = 2; else { free(ToolWindow); } } if (File_error == 0 && header.UserData == 1) { // Skip extended Drawer Data for OS 2.x if (fseek(file, 8, SEEK_CUR) != 0) File_error = 1; } // OS 3.5 (Glow Icon) can follow : // it is IFF data if (!has_NewIcons && img_count > 0) { if (Config.Clear_palette) memset(context->Palette,0,sizeof(T_Palette)); if (header.UserData == 0) memcpy(context->Palette, amigaOS1x_pal, sizeof(amigaOS1x_pal)); else memcpy(context->Palette, amigaOS2x_pal, sizeof(amigaOS2x_pal)); Pre_load(context, header.Width, header.Height,file_size,FORMAT_INFO,PIXEL_SIMPLE, imgheaders[0].Depth); Set_image_mode(context, IMAGE_MODE_ANIMATION); for (img_count = 0; img_count < 2 && buffers[img_count] != NULL; img_count++) { if (img_count > 0) { if (context->Type == CONTEXT_PREVIEW || context->Type == CONTEXT_PREVIEW_PALETTE) break; // don't load 2nd image in preview mode Set_loading_layer(context, img_count); } for (y_pos = 0; y_pos < imgheaders[img_count].Height; y_pos++) Draw_IFF_line(context, buffers[img_count] + y_pos * line_size, y_pos, plane_line_size << 3, imgheaders[img_count].Depth); } } for (img_count = 0; img_count < 2; img_count++) if (buffers[img_count] != NULL) { free(buffers[img_count]); buffers[img_count] = NULL; } } else File_error=1; fclose(file); } //////////////////////////////////// BMP //////////////////////////////////// /** * @defgroup BMP Bitmap and icon files * @ingroup loadsaveformats * .BMP/.ICO/.CUR files from OS/2 or Windows * * We support OS/2 files and windows BITMAPINFOHEADER, BITMAPV4HEADER, * BITMAPV5HEADER files. * * .ICO with PNG content are also supported * * @{ */ /// BMP file header typedef struct { byte Signature[2]; ///< ='BM' = 0x4D42 dword Size_1; ///< file size word Reserved_1; ///< 0 word Reserved_2; ///< 0 dword Offset; ///< Offset of bitmap data start dword Size_2; ///< BITMAPINFOHEADER size dword Width; ///< Image Width int32_t Height; ///< Image Height. signed: negative means a top-down bitmap (rare) word Planes; ///< Should be 1 word Nb_bits; ///< Bits per pixel : 1,2,4,8,16,24 or 32 dword Compression; ///< Known values : 0=BI_RGB, 1=BI_RLE8, 2=BI_RLE4, 3=BI_BITFIELDS, 4=BI_JPEG, 5=BI_PNG dword Size_3; ///< (optional) byte size of bitmap data dword XPM; ///< (optional) horizontal pixels-per-meter dword YPM; ///< (optional) vertical pixels-per-meter dword Nb_Clr; ///< number of color indexes used in the table. 0 for default (1 << Nb_bits) dword Clr_Imprt; ///< number of color indexes that are required for displaying the bitmap. 0 : all colors are required. } T_BMP_Header; /// Test for BMP format void Test_BMP(T_IO_Context * context, FILE * file) { T_BMP_Header header; (void)context; File_error=1; if (Read_bytes(file,&(header.Signature),2) // "BM" && Read_dword_le(file,&(header.Size_1)) && Read_word_le(file,&(header.Reserved_1)) && Read_word_le(file,&(header.Reserved_2)) && Read_dword_le(file,&(header.Offset)) && Read_dword_le(file,&(header.Size_2)) ) { if (header.Signature[0]=='B' && header.Signature[1]=='M' && ((header.Size_1 == (header.Size_2 + 14)) || // Size_1 is fixed to 14 + header Size in some OS/2 BMPs (header.Offset < header.Size_1 && header.Offset >= (14 + header.Size_2)))) { GFX2_Log(GFX2_DEBUG, "BMP : Size_1=%u Offset=%u Size_2=%u\n", header.Size_1, header.Offset, header.Size_2); if ( header.Size_2==40 /* WINDOWS BITMAPINFOHEADER */ || header.Size_2==12 /* OS/2 */ || header.Size_2==64 /* OS/2 v2 */ || header.Size_2==16 /* OS/2 v2 - short - */ || header.Size_2==108 /* Windows BITMAPV4HEADER */ || header.Size_2==124 /* Windows BITMAPV5HEADER */ ) { File_error=0; } } } } /// extract component value and properly shift it. static byte Bitmap_mask(dword pixel, dword mask, int bits, int shift) { dword value = (pixel & mask) >> shift; if (bits < 8) value = (value << (8 - bits)) | (value >> (2 * bits - 8)); else if (bits > 8) value >>= (bits - 8); return (byte)value; } /// Load the Palette for 1 to 8bpp BMP's static void Load_BMP_Palette(T_IO_Context * context, FILE * file, unsigned int nb_colors, int is_rgb24) { byte local_palette[256*4]; // R,G,B,0 or RGB unsigned int i, j; if (Read_bytes(file, local_palette, MIN(nb_colors, 256) * (is_rgb24?3:4))) { if (Config.Clear_palette) memset(context->Palette,0,sizeof(T_Palette)); // We can now load the new palette for (i = 0, j = 0; i < nb_colors && i < 256; i++) { context->Palette[i].B = local_palette[j++]; context->Palette[i].G = local_palette[j++]; context->Palette[i].R = local_palette[j++]; if (!is_rgb24) j++; } if (nb_colors > 256) // skip additional entries fseek(file, (nb_colors - 256) * (is_rgb24?3:4), SEEK_CUR); } else { File_error=1; } } /// rows are stored from the top to the bottom (standard for BMP is from bottom to the top) #define LOAD_BMP_PIXEL_FLAG_TOP_DOWN 0x01 /// We are decoding the AND-mask plane (transparency) of a .ICO file #define LOAD_BMP_PIXEL_FLAG_TRANSP_PLANE 0x02 static void Load_BMP_Pixels(T_IO_Context * context, FILE * file, unsigned int compression, unsigned int nbbits, int flags, const dword * mask) { unsigned int index; short x_pos; short y_pos; byte value; byte a,b; int bits[4]; int shift[4]; int i; // compute bit count and shift for masks for (i = 0; i < 4; i++) { if (mask[i] == 0) { bits[i] = 0; shift[i] = 0; } else { bits[i] = count_set_bits(mask[i]); shift[i] = count_trailing_zeros(mask[i]); } } switch (compression) { case 0 : // BI_RGB : No compression case 3 : // BI_BITFIELDS for (y_pos=0; (y_pos < context->Height && !File_error); y_pos++) { short target_y; target_y = (flags & LOAD_BMP_PIXEL_FLAG_TOP_DOWN) ? y_pos : context->Height-1-y_pos; switch (nbbits) { case 8 : for (x_pos = 0; x_pos < context->Width; x_pos++) { if (!Read_byte(file, &value)) File_error = 2; Set_pixel(context, x_pos, target_y, value); } break; case 4 : for (x_pos = 0; x_pos < context->Width; ) { if (!Read_byte(file, &value)) File_error = 2; Set_pixel(context, x_pos++, target_y, (value >> 4) & 0x0F); Set_pixel(context, x_pos++, target_y, value & 0x0F); } break; case 2: for (x_pos = 0; x_pos < context->Width; x_pos++) { if ((x_pos & 3) == 0) { if (!Read_byte(file, &value)) File_error = 2; } Set_pixel(context, x_pos, target_y, (value >> 6) & 3); value <<= 2; } break; case 1 : for (x_pos = 0; x_pos < context->Width; x_pos++) { if ((x_pos & 7) == 0) { if (!Read_byte(file, &value)) File_error = 2; } if (flags & LOAD_BMP_PIXEL_FLAG_TRANSP_PLANE) { if (value & 0x80) // transparent pixel ! Set_pixel(context, x_pos, target_y, context->Transparent_color); } else Set_pixel(context, x_pos, target_y, (value >> 7) & 1); value <<= 1; } break; case 24: for (x_pos = 0; x_pos < context->Width; x_pos++) { byte bgr[3]; if (!Read_bytes(file, bgr, 3)) File_error = 2; Set_pixel_24b(context, x_pos, target_y, bgr[2], bgr[1], bgr[0]); } break; case 32: for (x_pos = 0; x_pos < context->Width; x_pos++) { dword pixel; if (!Read_dword_le(file, &pixel)) File_error = 2; Set_pixel_24b(context, x_pos, target_y, Bitmap_mask(pixel,mask[0],bits[0],shift[0]), Bitmap_mask(pixel,mask[1],bits[1],shift[1]), Bitmap_mask(pixel,mask[2],bits[2],shift[2])); } break; case 16: for (x_pos = 0; x_pos < context->Width; x_pos++) { word pixel; if (!Read_word_le(file, &pixel)) File_error = 2; Set_pixel_24b(context, x_pos, target_y, Bitmap_mask(pixel,mask[0],bits[0],shift[0]), Bitmap_mask(pixel,mask[1],bits[1],shift[1]), Bitmap_mask(pixel,mask[2],bits[2],shift[2])); } break; } // lines are padded to dword sizes if (((context->Width * nbbits + 7) >> 3) & 3) fseek(file, 4 - (((context->Width * nbbits + 7) >> 3) & 3), SEEK_CUR); } break; case 1 : // BI_RLE8 Compression x_pos=0; y_pos=context->Height-1; /*Init_lecture();*/ if(Read_byte(file, &a)!=1 || Read_byte(file, &b)!=1) File_error=2; while (!File_error) { if (a) // Encoded mode for (index=1; index<=a; index++) Set_pixel(context, x_pos++,y_pos,b); else // Absolute mode switch (b) { case 0 : // End of line x_pos=0; y_pos--; break; case 1 : // End of bitmap break; case 2 : // Delta if(Read_byte(file, &a)!=1 || Read_byte(file, &b)!=1) File_error=2; x_pos+=a; y_pos-=b; break; default: // Nouvelle série while (b) { if(Read_byte(file, &a)!=1) File_error=2; //Read_one_byte(file, &c); Set_pixel(context, x_pos++,y_pos,a); //if (--c) //{ // Set_pixel(context, x_pos++,y_pos,c); // b--; //} b--; } if (ftell(file) & 1) fseek(file, 1, SEEK_CUR); } if (a==0 && b==1) break; if(Read_byte(file, &a) !=1 || Read_byte(file, &b)!=1) { File_error=2; } } break; case 2 : // BI_RLE4 Compression x_pos = 0; y_pos = context->Height-1; while (!File_error) { if(!(Read_byte(file, &a) && Read_byte(file, &b))) { File_error = 2; break; } if (a > 0) // Encoded mode : pixel count = a { //GFX2_Log(GFX2_DEBUG, "BI_RLE4: %d &%02X\n", a, b); for (index = 0; index < a; index++) Set_pixel(context, x_pos++, y_pos, ((index & 1) ? b : (b >> 4)) & 0x0f); } else { // a == 0 : Escape code byte c = 0; //GFX2_Log(GFX2_DEBUG, "BI_RLE4: %d %d\n", a, b); if (b == 1) // end of bitmap break; switch (b) { case 0 : //End of line x_pos = 0; y_pos--; break; case 2 : // Delta if(Read_byte(file, &a)!=1 || Read_byte(file, &b)!=1) File_error=2; x_pos += a; y_pos -= b; break; default: // Absolute mode : pixel count = b for (index = 0; index < b && !File_error; index++, x_pos++) { if (index&1) Set_pixel(context, x_pos,y_pos,c&0xF); else { if (!Read_byte(file, &c)) File_error=2; else Set_pixel(context, x_pos,y_pos,c>>4); } } if ((b + 1) & 2) { // read a pad byte to enforce word alignment if (!Read_byte(file, &c)) File_error = 2; } } } } break; case 5: // BI_PNG { byte png_header[8]; // Load header (8 first bytes) if (!Read_bytes(file,png_header,8)) File_error = 1; else { // Do we recognize a png file signature ? #ifndef __no_pnglib__ if (png_sig_cmp(png_header, 0, 8) == 0) { Load_PNG_Sub(context, file, NULL, 0); } #else if (0 == memcmp(png_header, "\x89PNG", 4)) { // NO PNG Support GFX2_Log(GFX2_WARNING, "PNG Signature : Compiled without libpng support\n"); File_error = 2; } #endif else { GFX2_Log(GFX2_WARNING, "No PNG signature in BI_PNG BMP\n"); File_error = 1; } } } break; default: GFX2_Log(GFX2_WARNING, "BMP: Unknown compression type %d\n", compression); } } /// Load BMP file void Load_BMP(T_IO_Context * context) { FILE *file; T_BMP_Header header; word nb_colors = 0; long file_size; byte negative_height; // top_down byte true_color = 0; dword mask[4]; // R G B A file = Open_file_read(context); if (file == NULL) { File_error = 1; return; } File_error = 0; file_size = File_length_file(file); /* Read header */ if (!(Read_bytes(file,header.Signature,2) && Read_dword_le(file,&(header.Size_1)) && Read_word_le(file,&(header.Reserved_1)) && Read_word_le(file,&(header.Reserved_2)) && Read_dword_le(file,&(header.Offset)) && Read_dword_le(file,&(header.Size_2)) )) { File_error = 1; } else { if (header.Size_2 == 40 /* WINDOWS BITMAPINFOHEADER*/ || header.Size_2 == 64 /* OS/2 v2 */ || header.Size_2 == 108 /* Windows BITMAPV4HEADER */ || header.Size_2 == 124 /* Windows BITMAPV5HEADER */) { if (!(Read_dword_le(file,&(header.Width)) && Read_dword_le(file,(dword *)&(header.Height)) && Read_word_le(file,&(header.Planes)) && Read_word_le(file,&(header.Nb_bits)) && Read_dword_le(file,&(header.Compression)) && Read_dword_le(file,&(header.Size_3)) && Read_dword_le(file,&(header.XPM)) && Read_dword_le(file,&(header.YPM)) && Read_dword_le(file,&(header.Nb_Clr)) && Read_dword_le(file,&(header.Clr_Imprt)) )) File_error = 1; else GFX2_Log(GFX2_DEBUG, "%s BMP %ux%d planes=%u bpp=%u compression=%u %u/%u\n", header.Size_2 == 64 ? "OS/2 v2" : "Windows", header.Width, header.Height, header.Planes, header.Nb_bits, header.Compression, header.XPM, header.YPM); } else if (header.Size_2 == 16) /* OS/2 v2 *short* */ { if (!(Read_dword_le(file,&(header.Width)) && Read_dword_le(file,(dword *)&(header.Height)) && Read_word_le(file,&(header.Planes)) && Read_word_le(file,&(header.Nb_bits)) )) File_error = 1; else { GFX2_Log(GFX2_DEBUG, "OS/2 v2 BMP %ux%d planes=%u bpp=%u *short header*\n", header.Width, header.Height, header.Planes, header.Nb_bits); header.Compression = 0; header.Size_3 = 0; header.XPM = 0; header.YPM = 0; header.Nb_Clr = 0; header.Clr_Imprt = 0; } } else if (header.Size_2 == 12 /* OS/2 */) { word tmp_width = 0, tmp_height = 0; if (Read_word_le(file,&tmp_width) && Read_word_le(file,&tmp_height) && Read_word_le(file,&(header.Planes)) && Read_word_le(file,&(header.Nb_bits))) { GFX2_Log(GFX2_DEBUG, "OS/2 BMP %ux%u planes=%u bpp=%u\n", tmp_width, tmp_height, header.Planes, header.Nb_bits); header.Width = tmp_width; header.Height = tmp_height; header.Compression = 0; header.Size_3 = 0; header.XPM = 0; header.YPM = 0; header.Nb_Clr = 0; header.Clr_Imprt = 0; } else File_error = 1; } else { GFX2_Log(GFX2_WARNING, "Unknown BMP type Size_2=%u\n", header.Size_2); File_error = 1; } } if (File_error == 0) { /* header was read */ switch (header.Nb_bits) { case 1 : case 2 : case 4 : case 8 : if (header.Nb_Clr) nb_colors = header.Nb_Clr; else nb_colors = 1 << header.Nb_bits; break; case 16: case 24: case 32: true_color = 1; break; case 0: if (header.Compression == 5) // Nb_bits is 0 with BI_PNG break; #if defined(__GNUC__) && (__GNUC__ >= 7) __attribute__ ((fallthrough)); #endif default: GFX2_Log(GFX2_WARNING, "BMP: Unsupported bit per pixel %u\n", header.Nb_bits); File_error = 1; } if (header.Height < 0) { negative_height = 1; header.Height = -header.Height; } else { negative_height = 0; } // Image 16/24/32 bits if (header.Nb_bits == 16) { mask[0] = 0x00007C00; mask[1] = 0x000003E0; mask[2] = 0x0000001F; } else { mask[0] = 0x00FF0000; mask[1] = 0x0000FF00; mask[2] = 0x000000FF; } mask[3] = 0; if (File_error == 0) { enum PIXEL_RATIO ratio = PIXEL_SIMPLE; if (header.XPM > 0 && header.YPM > 0) { if (header.XPM * 10 > header.YPM * 15) // XPM/YPM > 1.5 ratio = PIXEL_TALL; else if (header.XPM * 15 < header.YPM * 10) // XPM/YPM < 1/1.5 ratio = PIXEL_WIDE; } Pre_load(context, header.Width, header.Height, file_size, FORMAT_BMP, ratio, header.Nb_bits); if (File_error==0) { if (header.Size_2 == 64) fseek(file, header.Size_2 - 40, SEEK_CUR); if (header.Size_2 >= 108 || (true_color && header.Compression == 3)) // BI_BITFIELDS { if (!Read_dword_le(file,&mask[0]) || !Read_dword_le(file,&mask[1]) || !Read_dword_le(file,&mask[2])) File_error=2; if (header.Size_2 >= 108) { Read_dword_le(file,&mask[3]); // Alpha mask fseek(file, header.Size_2 - 40 - 16, SEEK_CUR); // skip extended v4/v5 header fields } GFX2_Log(GFX2_DEBUG, "BMP masks : R=%08x G=%08x B=%08x A=%08x\n", mask[0], mask[1], mask[2], mask[3]); } if (nb_colors > 0) Load_BMP_Palette(context, file, nb_colors, header.Size_2 == 12); if (File_error==0) { if (fseek(file, header.Offset, SEEK_SET)) File_error=2; else Load_BMP_Pixels(context, file, header.Compression, header.Nb_bits, negative_height ? LOAD_BMP_PIXEL_FLAG_TOP_DOWN : 0, mask); } } } } fclose(file); } /// Save BMP file void Save_BMP(T_IO_Context * context) { FILE *file; T_BMP_Header header; short x_pos; short y_pos; long line_size; word index; byte local_palette[256][4]; // R,G,B,0 File_error=0; // Ouverture du fichier if ((file=Open_file_write(context))) { setvbuf(file, NULL, _IOFBF, 64*1024); // Image width must be a multiple of 4 bytes line_size = context->Width; if (line_size & 3) line_size += (4 - (line_size & 3)); header.Signature[0] = 'B'; header.Signature[1] = 'M'; header.Size_1 =(line_size*context->Height)+1078; header.Reserved_1 =0; header.Reserved_2 =0; header.Offset =1078; // Size of header data (including palette) header.Size_2 =40; // Size of header header.Width =context->Width; header.Height =context->Height; header.Planes =1; header.Nb_bits =8; header.Compression=0; header.Size_3 =0; header.XPM =0; header.YPM =0; header.Nb_Clr =0; header.Clr_Imprt =0; if (Write_bytes(file,header.Signature,2) && Write_dword_le(file,header.Size_1) && Write_word_le(file,header.Reserved_1) && Write_word_le(file,header.Reserved_2) && Write_dword_le(file,header.Offset) && Write_dword_le(file,header.Size_2) && Write_dword_le(file,header.Width) && Write_dword_le(file,header.Height) && Write_word_le(file,header.Planes) && Write_word_le(file,header.Nb_bits) && Write_dword_le(file,header.Compression) && Write_dword_le(file,header.Size_3) && Write_dword_le(file,header.XPM) && Write_dword_le(file,header.YPM) && Write_dword_le(file,header.Nb_Clr) && Write_dword_le(file,header.Clr_Imprt)) { // Chez Bill, ils ont dit: "On va mettre les couleur dans l'ordre // inverse, et pour faire chier, on va les mettre sur une échelle de // 0 à 255 parce que le standard VGA c'est de 0 à 63 (logique!). Et // puis comme c'est pas assez débile, on va aussi y rajouter un octet // toujours à 0 pour forcer les gens à s'acheter des gros disques // durs... Comme ça, ça fera passer la pillule lorsqu'on sortira // Windows 95." ... for (index=0; index<256; index++) { local_palette[index][0]=context->Palette[index].B; local_palette[index][1]=context->Palette[index].G; local_palette[index][2]=context->Palette[index].R; local_palette[index][3]=0; } if (Write_bytes(file,local_palette,1024)) { // ... Et Bill, il a dit: "OK les gars! Mais seulement si vous rangez // les pixels dans l'ordre inverse, mais que sur les Y quand-même // parce que faut pas pousser." for (y_pos=context->Height-1; ((y_pos>=0) && (!File_error)); y_pos--) for (x_pos=0; x_pos 0) File_error=0; } } void Load_ICO(T_IO_Context * context) { FILE *file; struct { word Reserved; word Type; // Specifies image type: 1 for icon (.ICO) image, 2 for cursor (.CUR) image. Other values are invalid. word Count; // Specifies number of images in the file. } header; T_ICO_ImageEntry * images; T_ICO_ImageEntry * entry; unsigned int i; word width, max_width = 0; word max_bpp = 0; word min_bpp = 0xffff; dword mask[4]; // R G B A File_error=0; if ((file=Open_file_read(context))) { if (Read_word_le(file,&(header.Reserved)) && Read_word_le(file,&(header.Type)) && Read_word_le(file,&(header.Count))) { images = malloc(sizeof(T_ICO_ImageEntry) * header.Count); if (images == NULL) { fclose(file); File_error=1; return; } for (i = 0; i < header.Count; i++) { entry = images + i; if (!Read_byte(file,&entry->width) || !Read_byte(file,&entry->height) || !Read_byte(file,&entry->ncolors) || !Read_byte(file,&entry->reserved) || !Read_word_le(file,&entry->planes) || !Read_word_le(file,&entry->bpp) || !Read_dword_le(file,&entry->bytecount) || !Read_dword_le(file,&entry->offset)) { free(images); fclose(file); File_error=1; return; } width = entry->width; if (width == 0) width = 256; if (width > max_width) max_width = width; // For various reasons, 256x256 icons are all in PNG format, // and Microsoft decided PNG inside ICO should be in 32bit ARGB format... // https://blogs.msdn.microsoft.com/oldnewthing/20101022-00/?p=12473 GFX2_Log(GFX2_DEBUG, "%s #%02u %3ux%3u %ucols %ux%ubpp %u bytes at 0x%06x\n", (header.Type == 2) ? "CUR" : "ICO", i, width, entry->height, entry->ncolors, entry->planes, entry->bpp, entry->bytecount, entry->offset); } // select the picture with the maximum width and 256 colors or less for (i = 0; i < header.Count; i++) { if (images[i].width == (max_width & 0xff)) { if (header.Type == 2) // .CUR files have hotspot instead of planes/bpp in header break; if (images[i].bpp == 8) break; if (images[i].bpp < 8 && images[i].bpp > max_bpp) max_bpp = images[i].bpp; if (images[i].bpp < min_bpp) min_bpp = images[i].bpp; } } if (i >= header.Count && header.Type == 1) { // 256 color not found, select another one for (i = 0; i < header.Count; i++) { if (images[i].width == (max_width & 0xff)) { if ((max_bpp != 0 && images[i].bpp == max_bpp) || (images[i].bpp == min_bpp)) { break; } } } } if (i >= header.Count) { File_error=2; } else { byte png_header[8]; entry = images + i; GFX2_Log(GFX2_DEBUG, "Selected icon #%u at offset 0x%06x\n", i, entry->offset); fseek(file, entry->offset, SEEK_SET); // detect PNG icons // Load header (8 first bytes) if (!Read_bytes(file,png_header,8)) { File_error = 1; } else { // Do we recognize a png file signature ? #ifndef __no_pnglib__ if (png_sig_cmp(png_header, 0, 8) == 0) { Load_PNG_Sub(context, file, NULL, 0); } #else if (0 == memcmp(png_header, "\x89PNG", 4)) { // NO PNG Support GFX2_Log(GFX2_WARNING, "PNG Signature : Compiled without libpng support\n"); File_error = 2; } #endif else { T_BMP_Header bmpheader; fseek(file, -8, SEEK_CUR); // back // BMP if (Read_dword_le(file,&(bmpheader.Size_2)) // 40 && Read_dword_le(file,&(bmpheader.Width)) && Read_dword_le(file,(dword *)&(bmpheader.Height)) && Read_word_le(file,&(bmpheader.Planes)) && Read_word_le(file,&(bmpheader.Nb_bits)) && Read_dword_le(file,&(bmpheader.Compression)) && Read_dword_le(file,&(bmpheader.Size_3)) && Read_dword_le(file,&(bmpheader.XPM)) && Read_dword_le(file,&(bmpheader.YPM)) && Read_dword_le(file,&(bmpheader.Nb_Clr)) && Read_dword_le(file,&(bmpheader.Clr_Imprt)) ) { short real_height; word nb_colors = 0; GFX2_Log(GFX2_DEBUG, " BITMAPINFOHEADER %u %dx%d %ux%ubpp comp=%u\n", bmpheader.Size_2, bmpheader.Width, bmpheader.Height, bmpheader.Planes, bmpheader.Nb_bits, bmpheader.Compression); if (bmpheader.Nb_Clr != 0) nb_colors=bmpheader.Nb_Clr; else nb_colors=1<height if (real_height != entry->height) { GFX2_Log(GFX2_WARNING, "Load_ICO() : real_height(%hd) != entry->height(%hd)\n", real_height, entry->height); } // Image 16/24/32 bits if (bmpheader.Nb_bits == 16) { mask[0] = 0x00007C00; mask[1] = 0x000003E0; mask[2] = 0x0000001F; } else { mask[0] = 0x00FF0000; mask[1] = 0x0000FF00; mask[2] = 0x000000FF; } mask[3] = 0; Pre_load(context, bmpheader.Width,real_height,File_length_file(file),FORMAT_ICO,PIXEL_SIMPLE,bmpheader.Nb_bits); if (bmpheader.Nb_bits <= 8) Load_BMP_Palette(context, file, nb_colors, 0); else { if (bmpheader.Compression == 3) // BI_BITFIELDS { if (!Read_dword_le(file,&mask[0]) || !Read_dword_le(file,&mask[1]) || !Read_dword_le(file,&mask[2])) File_error=2; } } if (File_error == 0) { Load_BMP_Pixels(context, file, bmpheader.Compression, bmpheader.Nb_bits, (bmpheader.Height < 0) ? LOAD_BMP_PIXEL_FLAG_TOP_DOWN : 0, mask); // load transparency // TODO : load transparency for True color images too if (bmpheader.Nb_bits <= 8) { context->Transparent_color = 0xff; // TODO : pick an unused color if possible context->Background_transparent = 1; Load_BMP_Pixels(context, file, bmpheader.Compression, 1, (bmpheader.Height < 0) ? (LOAD_BMP_PIXEL_FLAG_TOP_DOWN|LOAD_BMP_PIXEL_FLAG_TRANSP_PLANE) : LOAD_BMP_PIXEL_FLAG_TRANSP_PLANE, mask); } } } } } } free(images); } fclose(file); } else { File_error=1; } } void Save_ICO(T_IO_Context * context) { FILE *file; short x_pos; short y_pos; long row_size; long row_size_mask; if (context->Width > 256 || context->Height > 256) { File_error=1; GFX2_Log(GFX2_WARNING, ".ICO files can handle images up to 256x256\n"); return; } File_error=0; if ((file=Open_file_write(context)) == NULL) File_error=1; else { row_size = (context->Width + 3) & ~3; // 8bpp (=1Byte) rounded up to dword row_size_mask = (((context->Width + 7) >> 3) + 3) & ~3; // 1bpp rounded up to dword // ICO Header if (!(Write_word_le(file,0) && // 0 Write_word_le(file,1) && // TYPE 1 = .ICO (2=.CUR) Write_word_le(file,1))) // Image count File_error=1; if (File_error == 0) { T_ICO_ImageEntry entry; // ICO image entry entry.width = context->Width & 0xff; //Specifies image width in pixels. Value 0 means image width is 256 pixels. entry.height = context->Height & 0xff;//Specifies image height in pixels. Value 0 means image height is 256 pixels. entry.ncolors = 0; entry.reserved = 0; entry.planes = 1; entry.bpp = 8; entry.bytecount = (row_size + row_size_mask) * context->Height + 40 + 1024; entry.offset = 6 + 16; if (!(Write_byte(file,entry.width) && Write_byte(file,entry.height) && Write_byte(file,entry.ncolors) && Write_byte(file,entry.reserved) && Write_word_le(file,entry.planes) && Write_word_le(file,entry.bpp) && Write_dword_le(file,entry.bytecount) && Write_dword_le(file,entry.offset))) File_error=1; } if (File_error == 0) { T_BMP_Header bmpheader; // BMP Header bmpheader.Size_2 = 40; bmpheader.Width = context->Width; bmpheader.Height = context->Height * 2; // *2 because of mask (transparency) data added after the pixel data bmpheader.Planes = 1; bmpheader.Nb_bits = 8; bmpheader.Compression = 0; bmpheader.Size_3 = 0; bmpheader.XPM = 0; bmpheader.YPM = 0; bmpheader.Nb_Clr = 0; bmpheader.Clr_Imprt = 0; if (!(Write_dword_le(file,bmpheader.Size_2) // 40 && Write_dword_le(file,bmpheader.Width) && Write_dword_le(file,bmpheader.Height) && Write_word_le(file,bmpheader.Planes) && Write_word_le(file,bmpheader.Nb_bits) && Write_dword_le(file,bmpheader.Compression) && Write_dword_le(file,bmpheader.Size_3) && Write_dword_le(file,bmpheader.XPM) && Write_dword_le(file,bmpheader.YPM) && Write_dword_le(file,bmpheader.Nb_Clr) && Write_dword_le(file,bmpheader.Clr_Imprt)) ) File_error=1; } if (File_error == 0) { int i; // palette for (i = 0; i < 256; i++) { if (!Write_dword_le(file, context->Palette[i].R << 16 | context->Palette[i].G << 8 | context->Palette[i].B)) { File_error=1; break; } } } if (File_error == 0) { // Image Data for (y_pos=context->Height-1; ((y_pos>=0) && (!File_error)); y_pos--) for (x_pos=0; x_posHeight-1; ((y_pos>=0) && (!File_error)); y_pos--) for (x_pos=0; x_posBackground_transparent && Get_pixel(context, x_pos,y_pos) == context->Transparent_color) value |= 1; // 1 = Transparent pixel if ((x_pos & 7) == 7) { Write_one_byte(file,value); } } } fclose(file); if (File_error != 0) Remove_file(context); } } /** @} */ //////////////////////////////////// PCX //////////////////////////////////// typedef struct { byte Manufacturer; // |_ Il font chier ces cons! Ils auraient pu byte Version; // | mettre une vraie signature! byte Compression; // L'image est-elle compressée? byte Depth; // Nombre de bits pour coder un pixel (inutile puisqu'on se sert de Plane) word X_min; // |_ Coin haut-gauche | word Y_min; // | de l'image |_ (Crétin!) word X_max; // |_ Coin bas-droit | word Y_max; // | de l'image | word X_dpi; // |_ Densité de |_ (Presque inutile parce que word Y_dpi; // | l'image | aucun moniteur n'est pareil!) byte Palette_16c[48]; // Palette 16 coul (inutile pour 256c) (débile!) byte Reserved; // Ca me plait ça aussi! byte Plane; // 4 => 16c , 1 => 256c , ... word Bytes_per_plane_line;// Doit toujours être pair word Palette_info; // 1 => color , 2 => Gris (ignoré à partir de la version 4) word Screen_X; // |_ Dimensions de word Screen_Y; // | l'écran d'origine byte Filler[54]; // Ca... J'adore! } T_PCX_Header; T_PCX_Header PCX_header; // -- Tester si un fichier est au format PCX -------------------------------- void Test_PCX(T_IO_Context * context, FILE * file) { (void)context; File_error=0; if (Read_byte(file,&(PCX_header.Manufacturer)) && Read_byte(file,&(PCX_header.Version)) && Read_byte(file,&(PCX_header.Compression)) && Read_byte(file,&(PCX_header.Depth)) && Read_word_le(file,&(PCX_header.X_min)) && Read_word_le(file,&(PCX_header.Y_min)) && Read_word_le(file,&(PCX_header.X_max)) && Read_word_le(file,&(PCX_header.Y_max)) && Read_word_le(file,&(PCX_header.X_dpi)) && Read_word_le(file,&(PCX_header.Y_dpi)) && Read_bytes(file,&(PCX_header.Palette_16c),48) && Read_byte(file,&(PCX_header.Reserved)) && Read_byte(file,&(PCX_header.Plane)) && Read_word_le(file,&(PCX_header.Bytes_per_plane_line)) && Read_word_le(file,&(PCX_header.Palette_info)) && Read_word_le(file,&(PCX_header.Screen_X)) && Read_word_le(file,&(PCX_header.Screen_Y)) && Read_bytes(file,&(PCX_header.Filler),54) ) { // Vu que ce header a une signature de merde et peu significative, il // va falloir que je teste différentes petites valeurs dont je connais // l'intervalle. Grrr! if ( (PCX_header.Manufacturer!=10) || (PCX_header.Compression>1) || ( (PCX_header.Depth!=1) && (PCX_header.Depth!=2) && (PCX_header.Depth!=4) && (PCX_header.Depth!=8) ) || ( (PCX_header.Plane!=1) && (PCX_header.Plane!=2) && (PCX_header.Plane!=4) && (PCX_header.Plane!=8) && (PCX_header.Plane!=3) ) || (PCX_header.X_maxWidth; x_pos++) { color=(buffer[x_pos/reduction]>>((reduction_minus_one-(x_pos%reduction))*depth)) & byte_mask; Set_pixel(context, x_pos,y_pos,color); } } // generate CGA RGBI colors. static void Set_CGA_Color(int i, T_Components * comp) { int intensity = (i & 8) ? 85 : 0; comp->R = ((i & 4) ? 170 : 0) + intensity; if (i == 6) comp->G = 85; // color 6 is brown instead of yellow on IBM CGA display else comp->G = ((i & 2) ? 170 : 0) + intensity; comp->B = ((i & 1) ? 170 : 0) + intensity; } void Load_PCX(T_IO_Context * context) { FILE *file; short line_size; short real_line_size; // width de l'image corrigée short width_read; short x_pos; short y_pos; byte byte1; byte byte2; byte index; dword nb_colors; long file_size; long position; long image_size; byte * buffer; File_error=0; if ((file=Open_file_read(context))) { file_size=File_length_file(file); if (Read_byte(file,&(PCX_header.Manufacturer)) && Read_byte(file,&(PCX_header.Version)) && Read_byte(file,&(PCX_header.Compression)) && Read_byte(file,&(PCX_header.Depth)) && Read_word_le(file,&(PCX_header.X_min)) && Read_word_le(file,&(PCX_header.Y_min)) && Read_word_le(file,&(PCX_header.X_max)) && Read_word_le(file,&(PCX_header.Y_max)) && Read_word_le(file,&(PCX_header.X_dpi)) && Read_word_le(file,&(PCX_header.Y_dpi)) && Read_bytes(file,&(PCX_header.Palette_16c),48) && Read_byte(file,&(PCX_header.Reserved)) && Read_byte(file,&(PCX_header.Plane)) && Read_word_le(file,&(PCX_header.Bytes_per_plane_line)) && Read_word_le(file,&(PCX_header.Palette_info)) && Read_word_le(file,&(PCX_header.Screen_X)) && Read_word_le(file,&(PCX_header.Screen_Y)) && Read_bytes(file,&(PCX_header.Filler),54) ) { Pre_load(context, PCX_header.X_max - PCX_header.X_min + 1, PCX_header.Y_max - PCX_header.Y_min + 1, file_size, FORMAT_PCX, PIXEL_SIMPLE, PCX_header.Plane * PCX_header.Depth); Original_screen_X = PCX_header.Screen_X; Original_screen_Y = PCX_header.Screen_Y; if (!(PCX_header.Plane==3 && PCX_header.Depth==8)) { if (File_error==0) { // On prépare la palette à accueillir les valeurs du fichier PCX if (Config.Clear_palette) memset(context->Palette,0,sizeof(T_Palette)); nb_colors=(dword)(1<Palette,PCX_header.Palette_16c,48); if (nb_colors<=4) { // CGA ! int i; if (PCX_header.Version < 5 // Detect if the palette is usable || (nb_colors == 4 && (PCX_header.Palette_16c[6]&15) == 0 && (PCX_header.Palette_16c[7]&15) == 0 && (PCX_header.Palette_16c[8]&15) == 0 && (PCX_header.Palette_16c[9]&15) == 0 && (PCX_header.Palette_16c[10]&15) == 0 && (PCX_header.Palette_16c[11]&15) == 0) || (nb_colors == 2 && PCX_header.Palette_16c[1] == 0 && PCX_header.Palette_16c[2] == 0)) { // special CGA palette meaning : if (nb_colors == 2) { // Background : BLACK context->Palette[0].R=0; context->Palette[0].G=0; context->Palette[0].B=0; // Foreground : 4 MSB of palette[0] is index of the CGA color to use. i = (PCX_header.Palette_16c[0] >> 4); if (i==0) i = 15; // Bright White by default Set_CGA_Color(i, &context->Palette[1]); } else { // Color CGA // background color : 4 MSB of palette[0] Set_CGA_Color((PCX_header.Palette_16c[0] >> 4), &context->Palette[0]); // Palette_16c[3] : 8 bits CPIx xxxx // C bit : Color burst enabled => disable it to set 3rd palette // P bit : palette : 0 = yellow/ 1 = white // I bit : intensity // CGA Palette 0 : 2 green, 4 red, 6 brown // CGA Palette 1 : 3 cyan, 5 magenta, 7 white // CGA 3rd palette : 3 cyan, 4 red, 7 white // After some tests in PC Paintbrush 3.11, it looks like // the Color burst bit is not taken into acount. i = 2; // 2 - CGA Green if (PCX_header.Palette_16c[3] & 0x40) i++; // Palette 1 (3 = cyan) if (PCX_header.Palette_16c[3] & 0x20) i += 8; // High intensity Set_CGA_Color(i++, &context->Palette[1]); i++; // Skip 1 color Set_CGA_Color(i++, &context->Palette[2]); i++; // Skip 1 color Set_CGA_Color(i, &context->Palette[3]); } } } // On se positionne à la fin du fichier - 769 octets pour voir s'il y // a une palette. if ( (PCX_header.Depth==8) && (PCX_header.Version>=5) && (file_size>(256*3+128)) ) { fseek(file,file_size-((256*3)+1),SEEK_SET); // On regarde s'il y a une palette après les données de l'image if (Read_byte(file,&byte1)) if (byte1==12) // Lire la palette si c'est une image en 256 couleurs { int index; // On lit la palette 256c que ces crétins ont foutue à la fin du fichier for(index=0;index<256;index++) if ( ! Read_byte(file,&(context->Palette[index].R)) || ! Read_byte(file,&(context->Palette[index].G)) || ! Read_byte(file,&(context->Palette[index].B)) ) { File_error=2; GFX2_Log(GFX2_ERROR, "ERROR READING PCX PALETTE ! index=%d\n", index); break; } } } // Maintenant qu'on a lu la palette que ces crétins sont allés foutre // à la fin, on retourne juste après le header pour lire l'image. fseek(file,128,SEEK_SET); if (!File_error) { line_size=PCX_header.Bytes_per_plane_line*PCX_header.Plane; real_line_size=(short)PCX_header.Bytes_per_plane_line<<3; // On se sert de données ILBM car le dessin de ligne en moins de 256 // couleurs se fait comme avec la structure ILBM. buffer=(byte *)malloc(line_size); // Chargement de l'image if (PCX_header.Compression) // Image compressée { /*Init_lecture();*/ image_size=(long)PCX_header.Bytes_per_plane_line*context->Height; if (PCX_header.Depth==8) // 256 couleurs (1 plan) { for (position=0; ((positionHeight) && (!File_error)); y_pos++) { for (x_pos=0; ((x_posHeight) && (!File_error);y_pos++) { if ((width_read=Read_bytes(file,buffer,line_size))) { if (PCX_header.Plane==1) for (x_pos=0; x_posWidth;x_pos++) Set_pixel(context, x_pos,y_pos,buffer[x_pos]); else { if (PCX_header.Depth==1) Draw_IFF_line(context, buffer, y_pos,real_line_size,PCX_header.Plane); else Draw_PCX_line(context, buffer, y_pos,PCX_header.Depth); } } else File_error=2; } } free(buffer); } } } else { // Image 24 bits!!! if (File_error==0) { line_size=PCX_header.Bytes_per_plane_line*3; buffer=(byte *)malloc(line_size); if (!PCX_header.Compression) { for (y_pos=0;(y_posHeight) && (!File_error);y_pos++) { if (Read_bytes(file,buffer,line_size)) { for (x_pos=0; x_posWidth; x_pos++) Set_pixel_24b(context, x_pos,y_pos,buffer[x_pos+(PCX_header.Bytes_per_plane_line*0)],buffer[x_pos+(PCX_header.Bytes_per_plane_line*1)],buffer[x_pos+(PCX_header.Bytes_per_plane_line*2)]); } else File_error=2; } } else { /*Init_lecture();*/ for (y_pos=0,position=0;(y_posHeight) && (!File_error);) { // Lecture et décompression de la ligne if(Read_byte(file,&byte1)!=1) File_error=2; if (!File_error) { if ((byte1 & 0xC0)==0xC0) { byte1-=0xC0; // facteur de répétition if(Read_byte(file,&byte2)!=1) File_error=2; // octet à répéter if (!File_error) { for (index=0; (index=line_size) { for (x_pos=0; x_posWidth; x_pos++) Set_pixel_24b(context, x_pos,y_pos,buffer[x_pos+(PCX_header.Bytes_per_plane_line*0)],buffer[x_pos+(PCX_header.Bytes_per_plane_line*1)],buffer[x_pos+(PCX_header.Bytes_per_plane_line*2)]); y_pos++; position=0; } } } } else { buffer[position++]=byte1; if (position>=line_size) { for (x_pos=0; x_posWidth; x_pos++) Set_pixel_24b(context, x_pos,y_pos,buffer[x_pos+(PCX_header.Bytes_per_plane_line*0)],buffer[x_pos+(PCX_header.Bytes_per_plane_line*1)],buffer[x_pos+(PCX_header.Bytes_per_plane_line*2)]); y_pos++; position=0; } } } } if (position!=0) File_error=2; /*Close_lecture();*/ } free(buffer); buffer = NULL; } } } else { File_error=1; } fclose(file); } else File_error=1; } // -- Ecrire un fichier au format PCX --------------------------------------- void Save_PCX(T_IO_Context * context) { FILE *file; short line_size; short x_pos; short y_pos; byte counter; byte last_pixel; byte pixel_read; File_error=0; if ((file=Open_file_write(context))) { setvbuf(file, NULL, _IOFBF, 64*1024); PCX_header.Manufacturer=10; PCX_header.Version=5; PCX_header.Compression=1; PCX_header.Depth=8; PCX_header.X_min=0; PCX_header.Y_min=0; PCX_header.X_max=context->Width-1; PCX_header.Y_max=context->Height-1; PCX_header.X_dpi=0; PCX_header.Y_dpi=0; memcpy(PCX_header.Palette_16c,context->Palette,48); PCX_header.Reserved=0; PCX_header.Plane=1; PCX_header.Bytes_per_plane_line=(context->Width&1)?context->Width+1:context->Width; PCX_header.Palette_info=1; PCX_header.Screen_X=Screen_width; PCX_header.Screen_Y=Screen_height; memset(PCX_header.Filler,0,54); if (Write_bytes(file,&(PCX_header.Manufacturer),1) && Write_bytes(file,&(PCX_header.Version),1) && Write_bytes(file,&(PCX_header.Compression),1) && Write_bytes(file,&(PCX_header.Depth),1) && Write_word_le(file,PCX_header.X_min) && Write_word_le(file,PCX_header.Y_min) && Write_word_le(file,PCX_header.X_max) && Write_word_le(file,PCX_header.Y_max) && Write_word_le(file,PCX_header.X_dpi) && Write_word_le(file,PCX_header.Y_dpi) && Write_bytes(file,&(PCX_header.Palette_16c),48) && Write_bytes(file,&(PCX_header.Reserved),1) && Write_bytes(file,&(PCX_header.Plane),1) && Write_word_le(file,PCX_header.Bytes_per_plane_line) && Write_word_le(file,PCX_header.Palette_info) && Write_word_le(file,PCX_header.Screen_X) && Write_word_le(file,PCX_header.Screen_Y) && Write_bytes(file,&(PCX_header.Filler),54) ) { line_size=PCX_header.Bytes_per_plane_line*PCX_header.Plane; for (y_pos=0; ((y_posHeight) && (!File_error)); y_pos++) { pixel_read=Get_pixel(context, 0,y_pos); // Compression et écriture de la ligne for (x_pos=0; ((x_pos1) || (last_pixel>=0xC0) ) Write_one_byte(file,counter|0xC0); Write_one_byte(file,last_pixel); } } // Ecriture de l'octet (12) indiquant que la palette arrive if (!File_error) Write_one_byte(file,12); // Ecriture de la palette if (!File_error) { if (! Write_bytes(file,context->Palette,sizeof(T_Palette))) File_error=1; } } else File_error=1; fclose(file); if (File_error) Remove_file(context); } else File_error=1; } //////////////////////////////////// SCx //////////////////////////////////// /** * @defgroup SCx SCx format * @ingroup loadsaveformats * ColoRix VGA Paint SCx File Format * * file extensions are sci, scq, scf, scn, sco * @{ */ /// SCx header data typedef struct { byte Filler1[4]; ///< "RIX3" word Width; ///< Image Width word Height; ///< Image Height byte PaletteType; ///< M P RGB PIX 0xAF = VGA byte StorageType; ///< 00 = Linear (1 byte per pixel) 01,02 Planar 03 text 80 Compressed 40 extension block 20 encrypted } T_SCx_Header; /// Test if a file is SCx format void Test_SCx(T_IO_Context * context, FILE * file) { T_SCx_Header SCx_header; (void)context; File_error=1; // read and check header if (Read_bytes(file,SCx_header.Filler1,4) && Read_word_le(file, &(SCx_header.Width)) && Read_word_le(file, &(SCx_header.Height)) && Read_byte(file, &(SCx_header.PaletteType)) && Read_byte(file, &(SCx_header.StorageType)) ) { if ( (!memcmp(SCx_header.Filler1,"RIX",3)) && SCx_header.Width && SCx_header.Height) File_error=0; } } /// Read a SCx file void Load_SCx(T_IO_Context * context) { FILE *file; word x_pos,y_pos; long size,real_size; long file_size; T_SCx_Header SCx_header; T_Palette SCx_Palette; byte * buffer; byte bpp; File_error=0; if ((file=Open_file_read(context))) { file_size=File_length_file(file); if (Read_bytes(file,SCx_header.Filler1,4) && Read_word_le(file, &(SCx_header.Width)) && Read_word_le(file, &(SCx_header.Height)) && Read_byte(file, &(SCx_header.PaletteType)) && Read_byte(file, &(SCx_header.StorageType)) ) { bpp = (SCx_header.PaletteType & 7) + 1; // Bit per RGB component in palette = ((SCx_header.PaletteType >> 3) & 7) Pre_load(context, SCx_header.Width,SCx_header.Height,file_size,FORMAT_SCx,PIXEL_SIMPLE,bpp); size=sizeof(T_Components)*(1 << bpp); if (SCx_header.PaletteType & 0x80) { if (!Read_bytes(file, SCx_Palette, size)) File_error = 2; else { if (Config.Clear_palette) memset(context->Palette,0,sizeof(T_Palette)); Palette_64_to_256(SCx_Palette); memcpy(context->Palette,SCx_Palette,size); } } if (File_error == 0) { if (SCx_header.StorageType == 0x80) { GFX2_Log(GFX2_WARNING, "Compressed SCx files are not supported\n"); File_error = 2; } else { if (SCx_header.StorageType == 0) { // 256 couleurs (raw) buffer=(byte *)malloc(context->Width); for (y_pos=0;(y_posHeight) && (!File_error);y_pos++) { if (Read_bytes(file,buffer,context->Width)) for (x_pos=0; x_posWidth;x_pos++) Set_pixel(context, x_pos,y_pos,buffer[x_pos]); else File_error=2; } } else { // moins de 256 couleurs (planar) size=((context->Width+7)>>3)*bpp; real_size=(size/bpp)<<3; buffer=(byte *)malloc(size); for (y_pos=0;(y_posHeight) && (!File_error);y_pos++) { if (Read_bytes(file,buffer,size)) Draw_IFF_line(context, buffer, y_pos,real_size,bpp); else File_error=2; } } free(buffer); } } } else File_error=1; fclose(file); } else File_error=1; } /// Save a SCx file void Save_SCx(T_IO_Context * context) { FILE *file; short x_pos,y_pos; T_SCx_Header SCx_header; size_t last_char; // replace the '?' in file extension with the right letter last_char = strlen(context->File_name) - 1; if (context->File_name[last_char] == '?') { if (context->Width<=320) context->File_name[last_char]='I'; else { if (context->Width<=360) context->File_name[last_char]='Q'; else { if (context->Width<=640) context->File_name[last_char]='F'; else { if (context->Width<=800) context->File_name[last_char]='N'; else context->File_name[last_char]='O'; } } } // makes it same case as the previous character if (last_char > 0) context->File_name[last_char] |= (context->File_name[last_char - 1] & 32); // also fix the unicode file name if (context->File_name_unicode != NULL && context->File_name_unicode[0] != 0) { size_t ulen = Unicode_strlen(context->File_name_unicode); if (ulen > 1) context->File_name_unicode[ulen - 1] = context->File_name[last_char]; } } file = Open_file_write(context); if (file != NULL) { T_Palette palette_64; File_error = 0; memcpy(palette_64, context->Palette, sizeof(T_Palette)); Palette_256_to_64(palette_64); memcpy(SCx_header.Filler1,"RIX3",4); SCx_header.Width=context->Width; SCx_header.Height=context->Height; SCx_header.PaletteType=0xAF; SCx_header.StorageType=0x00; if (Write_bytes(file,SCx_header.Filler1, 4) && Write_word_le(file, SCx_header.Width) && Write_word_le(file, SCx_header.Height) && Write_byte(file, SCx_header.PaletteType) && Write_byte(file, SCx_header.StorageType) && Write_bytes(file,&palette_64,sizeof(T_Palette)) ) { for (y_pos=0; ((y_posHeight) && (!File_error)); y_pos++) for (x_pos=0; x_posWidth; x_pos++) Write_one_byte(file, Get_pixel(context, x_pos, y_pos)); } else { File_error = 1; } fclose(file); if (File_error) Remove_file(context); } else { File_error=1; } } /** @} */ //////////////////////////////////// XPM //////////////////////////////////// void Save_XPM(T_IO_Context* context) { // XPM are unix files, so use LF '\n' line endings FILE* file; int i,j; byte max_color = 0; word color_count; File_error = 0; file = Open_file_write(context); if (file == NULL) { File_error = 1; return; } setvbuf(file, NULL, _IOFBF, 64*1024); // in case there are less colors than 256, we could // optimize, and use only 1 character per pixel if possible // printable characters are from 0x20 to 0x7e, minus " 0x22 and \ 0x5c #define XPM_USABLE_CHARS (0x7f - 0x20 - 2) for (j = 0; j < context->Height; j++) for (i = 0; i < context->Width; i++) { byte value = Get_pixel(context, i, j); if (value > max_color) max_color = value; } color_count = (word)max_color + 1; fprintf(file, "/* XPM */\nstatic char* pixmap[] = {\n"); fprintf(file, "\"%d %d %d %d\",\n", context->Width, context->Height, color_count, color_count > XPM_USABLE_CHARS ? 2 : 1); if (color_count > XPM_USABLE_CHARS) { for (i = 0; i < color_count; i++) { if (context->Background_transparent && (i == context->Transparent_color)) fprintf(file, "\"%2.2X\tc None\",\n", i); // None is for transparent color else fprintf(file,"\"%2.2X\tc #%2.2x%2.2x%2.2x\",\n", i, context->Palette[i].R, context->Palette[i].G, context->Palette[i].B); } for (j = 0; j < context->Height; j++) { fprintf(file, "\""); for (i = 0; i < context->Width; i++) fprintf(file, "%2.2X", Get_pixel(context, i, j)); if (j == (context->Height - 1)) fprintf(file,"\"\n"); else fprintf(file,"\",\n"); } } else { int c; for (i = 0; i < color_count; i++) { c = (i < 2) ? i + 0x20 : i + 0x21; if (c >= 0x5c) c++; if (context->Background_transparent && (i == context->Transparent_color)) fprintf(file, "\"%c\tc None\",\n", c); // None is for transparent color else fprintf(file,"\"%c\tc #%2.2x%2.2x%2.2x\",\n", c, context->Palette[i].R, context->Palette[i].G, context->Palette[i].B); } for (j = 0; j < context->Height; j++) { fprintf(file, "\""); for (i = 0; i < context->Width; i++) { c = Get_pixel(context, i, j); c = (c < 2) ? c + 0x20 : c + 0x21; if (c >= 0x5c) c++; fprintf(file, "%c", c); } if (j == (context->Height - 1)) fprintf(file,"\"\n"); else fprintf(file,"\",\n"); } } fprintf(file, "};\n"); fclose(file); }