| // ========================================================== | |
| // G3 Fax Loader | |
| // | |
| // Design and implementation by | |
| // - Hervé Drolon (drolon@infonie.fr) | |
| // - Petr Pytelka (pyta@lightcomp.com) | |
| // | |
| // This file is part of FreeImage 3 | |
| // | |
| // COVERED CODE IS PROVIDED UNDER THIS LICENSE ON AN "AS IS" BASIS, WITHOUT WARRANTY | |
| // OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION, WARRANTIES | |
| // THAT THE COVERED CODE IS FREE OF DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE | |
| // OR NON-INFRINGING. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE COVERED | |
| // CODE IS WITH YOU. SHOULD ANY COVERED CODE PROVE DEFECTIVE IN ANY RESPECT, YOU (NOT | |
| // THE INITIAL DEVELOPER OR ANY OTHER CONTRIBUTOR) ASSUME THE COST OF ANY NECESSARY | |
| // SERVICING, REPAIR OR CORRECTION. THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL | |
| // PART OF THIS LICENSE. NO USE OF ANY COVERED CODE IS AUTHORIZED HEREUNDER EXCEPT UNDER | |
| // THIS DISCLAIMER. | |
| // | |
| // Use at your own risk! | |
| // ========================================================== | |
| #include "third_party/tiff/libtiff/tiffiop.h" | |
| #include "FreeImage.h" | |
| #include "Utilities.h" | |
| // ========================================================== | |
| // Plugin Interface | |
| // ========================================================== | |
| static int s_format_id; | |
| // ========================================================== | |
| // Constant/Macro declarations | |
| // ========================================================== | |
| #define G3_DEFAULT_WIDTH 1728 | |
| #define TIFFhowmany8(x) (((x)&0x07)?((uint32)(x)>>3)+1:(uint32)(x)>>3) | |
| // ========================================================== | |
| // libtiff interface | |
| // ========================================================== | |
| static tmsize_t | |
| _g3ReadProc(thandle_t handle, void *buf, tmsize_t size) { | |
| // returns an error when reading the TIFF header | |
| return 0; | |
| } | |
| static tmsize_t | |
| _g3WriteProc(thandle_t handle, void *buf, tmsize_t size) { | |
| // returns ok when writing the TIFF header | |
| return size; | |
| } | |
| static toff_t | |
| _g3SeekProc(thandle_t handle, toff_t off, int whence) { | |
| return 0; | |
| } | |
| static int | |
| _g3CloseProc(thandle_t handle) { | |
| return 0; | |
| } | |
| static toff_t | |
| _g3SizeProc(thandle_t handle) { | |
| return 0; | |
| } | |
| static int | |
| _g3MapProc(thandle_t, void** base, toff_t* size) { | |
| return 0; | |
| } | |
| static void | |
| _g3UnmapProc(thandle_t, void* base, toff_t size) { | |
| } | |
| // -------------------------------------------------------------- | |
| static tmsize_t | |
| G3GetFileSize(FreeImageIO *io, fi_handle handle) { | |
| long currentPos = io->tell_proc(handle); | |
| io->seek_proc(handle, 0, SEEK_END); | |
| long fileSize = io->tell_proc(handle); | |
| io->seek_proc(handle, currentPos, SEEK_SET); | |
| return fileSize; | |
| } | |
| static BOOL | |
| G3ReadFile(FreeImageIO *io, fi_handle handle, uint8 *tif_rawdata, tmsize_t tif_rawdatasize) { | |
| return ((tmsize_t)(io->read_proc(tif_rawdata, (unsigned)tif_rawdatasize, 1, handle) * tif_rawdatasize) == tif_rawdatasize); | |
| } | |
| // ========================================================== | |
| // Internal functions | |
| // ========================================================== | |
| static int | |
| copyFaxFile(FreeImageIO *io, fi_handle handle, TIFF* tifin, uint32 xsize, int stretch, FIMEMORY *memory) { | |
| BYTE *rowbuf = NULL; | |
| BYTE *refbuf = NULL; | |
| uint32 row; | |
| uint16 badrun; | |
| uint16 badfaxrun; | |
| uint32 badfaxlines; | |
| int ok; | |
| try { | |
| uint32 linesize = TIFFhowmany8(xsize); | |
| rowbuf = (BYTE*) _TIFFmalloc(linesize); | |
| refbuf = (BYTE*) _TIFFmalloc(linesize); | |
| if (rowbuf == NULL || refbuf == NULL) { | |
| throw FI_MSG_ERROR_MEMORY; | |
| } | |
| tifin->tif_rawdatasize = G3GetFileSize(io, handle); | |
| tifin->tif_rawdata = (tidata_t) _TIFFmalloc(tifin->tif_rawdatasize); | |
| if (tifin->tif_rawdata == NULL) { | |
| throw FI_MSG_ERROR_MEMORY; | |
| } | |
| if(!G3ReadFile(io, handle, tifin->tif_rawdata, tifin->tif_rawdatasize)) { | |
| throw "Read error at scanline 0"; | |
| } | |
| tifin->tif_rawcp = tifin->tif_rawdata; | |
| tifin->tif_rawcc = tifin->tif_rawdatasize; | |
| (*tifin->tif_setupdecode)(tifin); | |
| (*tifin->tif_predecode)(tifin, (uint16) 0); | |
| tifin->tif_row = 0; | |
| badfaxlines = 0; | |
| badfaxrun = 0; | |
| _TIFFmemset(refbuf, 0, linesize); | |
| row = 0; | |
| badrun = 0; // current run of bad lines | |
| while (tifin->tif_rawcc > 0) { | |
| ok = (*tifin->tif_decoderow)(tifin, rowbuf, linesize, 0); | |
| if (!ok) { | |
| badfaxlines++; | |
| badrun++; | |
| // regenerate line from previous good line | |
| _TIFFmemcpy(rowbuf, refbuf, linesize); | |
| } else { | |
| if (badrun > badfaxrun) | |
| badfaxrun = badrun; | |
| badrun = 0; | |
| _TIFFmemcpy(refbuf, rowbuf, linesize); | |
| } | |
| tifin->tif_row++; | |
| FreeImage_WriteMemory(rowbuf, linesize, 1, memory); | |
| row++; | |
| if (stretch) { | |
| FreeImage_WriteMemory(rowbuf, linesize, 1, memory); | |
| row++; | |
| } | |
| } | |
| if (badrun > badfaxrun) | |
| badfaxrun = badrun; | |
| _TIFFfree(tifin->tif_rawdata); | |
| tifin->tif_rawdata = NULL; | |
| _TIFFfree(rowbuf); | |
| _TIFFfree(refbuf); | |
| /* | |
| if (verbose) { | |
| fprintf(stderr, "%d rows in input\n", rows); | |
| fprintf(stderr, "%ld total bad rows\n", (long) badfaxlines); | |
| fprintf(stderr, "%d max consecutive bad rows\n", badfaxrun); | |
| } | |
| */ | |
| } catch(const char *message) { | |
| if(rowbuf) _TIFFfree(rowbuf); | |
| if(refbuf) _TIFFfree(refbuf); | |
| if(tifin->tif_rawdata) { | |
| _TIFFfree(tifin->tif_rawdata); | |
| tifin->tif_rawdata = NULL; | |
| } | |
| FreeImage_OutputMessageProc(s_format_id, message); | |
| return -1; | |
| } | |
| return (row); | |
| } | |
| // ========================================================== | |
| // Plugin Implementation | |
| // ========================================================== | |
| static const char * DLL_CALLCONV | |
| Format() { | |
| return "G3"; | |
| } | |
| static const char * DLL_CALLCONV | |
| Description() { | |
| return "Raw fax format CCITT G.3"; | |
| } | |
| static const char * DLL_CALLCONV | |
| Extension() { | |
| return "g3"; | |
| } | |
| static const char * DLL_CALLCONV | |
| RegExpr() { | |
| return NULL; // there is now reasonable regexp for raw G3 | |
| } | |
| static const char * DLL_CALLCONV | |
| MimeType() { | |
| return "image/fax-g3"; | |
| } | |
| static BOOL DLL_CALLCONV | |
| SupportsExportDepth(int depth) { | |
| return FALSE; | |
| } | |
| // ---------------------------------------------------------- | |
| static FIBITMAP * DLL_CALLCONV | |
| Load(FreeImageIO *io, fi_handle handle, int page, int flags, void *data) { | |
| TIFF *faxTIFF = NULL; | |
| FIBITMAP *dib = NULL; | |
| FIMEMORY *memory = NULL; | |
| //int verbose = 0; | |
| int stretch = 0; | |
| int rows; | |
| float resX = 204.0; | |
| float resY = 196.0; | |
| uint32 xsize = G3_DEFAULT_WIDTH; | |
| int compression_in = COMPRESSION_CCITTFAX3; | |
| int fillorder_in = FILLORDER_LSB2MSB; | |
| uint32 group3options_in = 0; // 1d-encoded | |
| uint32 group4options_in = 0; // compressed | |
| int photometric_in = PHOTOMETRIC_MINISWHITE; | |
| if(handle==NULL) return NULL; | |
| try { | |
| // set default load options | |
| compression_in = COMPRESSION_CCITTFAX3; // input is g3-encoded | |
| group3options_in &= ~GROUP3OPT_2DENCODING; // input is 1d-encoded (g3 only) | |
| fillorder_in = FILLORDER_MSB2LSB; // input has msb-to-lsb fillorder | |
| /* | |
| Original input-related fax2tiff options | |
| while ((c = getopt(argc, argv, "R:X:o:1234ABLMPUW5678abcflmprsuvwz?")) != -1) { | |
| switch (c) { | |
| // input-related options | |
| case '3': // input is g3-encoded | |
| compression_in = COMPRESSION_CCITTFAX3; | |
| break; | |
| case '4': // input is g4-encoded | |
| compression_in = COMPRESSION_CCITTFAX4; | |
| break; | |
| case 'U': // input is uncompressed (g3 and g4) | |
| group3options_in |= GROUP3OPT_UNCOMPRESSED; | |
| group4options_in |= GROUP4OPT_UNCOMPRESSED; | |
| break; | |
| case '1': // input is 1d-encoded (g3 only) | |
| group3options_in &= ~GROUP3OPT_2DENCODING; | |
| break; | |
| case '2': // input is 2d-encoded (g3 only) | |
| group3options_in |= GROUP3OPT_2DENCODING; | |
| break; | |
| case 'P': // input has not-aligned EOL (g3 only) | |
| group3options_in &= ~GROUP3OPT_FILLBITS; | |
| break; | |
| case 'A': // input has aligned EOL (g3 only) | |
| group3options_in |= GROUP3OPT_FILLBITS; | |
| break; | |
| case 'W': // input has 0 mean white | |
| photometric_in = PHOTOMETRIC_MINISWHITE; | |
| break; | |
| case 'B': // input has 0 mean black | |
| photometric_in = PHOTOMETRIC_MINISBLACK; | |
| break; | |
| case 'L': // input has lsb-to-msb fillorder | |
| fillorder_in = FILLORDER_LSB2MSB; | |
| break; | |
| case 'M': // input has msb-to-lsb fillorder | |
| fillorder_in = FILLORDER_MSB2LSB; | |
| break; | |
| case 'R': // input resolution | |
| resY = (float) atof(optarg); | |
| break; | |
| case 'X': // input width | |
| xsize = (uint32) atoi(optarg); | |
| break; | |
| // output-related options | |
| case 's': // stretch image by dup'ng scanlines | |
| stretch = 1; | |
| break; | |
| case 'v': // -v for info | |
| verbose++; | |
| break; | |
| } | |
| } | |
| */ | |
| // open a temporary memory buffer to save decoded scanlines | |
| memory = FreeImage_OpenMemory(); | |
| if(!memory) throw FI_MSG_ERROR_MEMORY; | |
| // wrap the raw fax file | |
| faxTIFF = TIFFClientOpen("(FakeInput)", "w", | |
| // TIFFClientOpen() fails if we don't set existing value here | |
| NULL, | |
| _g3ReadProc, _g3WriteProc, | |
| _g3SeekProc, _g3CloseProc, | |
| _g3SizeProc, _g3MapProc, | |
| _g3UnmapProc); | |
| if (faxTIFF == NULL) { | |
| throw "Can not create fake input file"; | |
| } | |
| TIFFSetMode(faxTIFF, O_RDONLY); | |
| TIFFSetField(faxTIFF, TIFFTAG_IMAGEWIDTH, xsize); | |
| TIFFSetField(faxTIFF, TIFFTAG_SAMPLESPERPIXEL, 1); | |
| TIFFSetField(faxTIFF, TIFFTAG_BITSPERSAMPLE, 1); | |
| TIFFSetField(faxTIFF, TIFFTAG_FILLORDER, fillorder_in); | |
| TIFFSetField(faxTIFF, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG); | |
| TIFFSetField(faxTIFF, TIFFTAG_PHOTOMETRIC, photometric_in); | |
| TIFFSetField(faxTIFF, TIFFTAG_YRESOLUTION, resY); | |
| TIFFSetField(faxTIFF, TIFFTAG_RESOLUTIONUNIT, RESUNIT_INCH); | |
| // NB: this must be done after directory info is setup | |
| TIFFSetField(faxTIFF, TIFFTAG_COMPRESSION, compression_in); | |
| if (compression_in == COMPRESSION_CCITTFAX3) | |
| TIFFSetField(faxTIFF, TIFFTAG_GROUP3OPTIONS, group3options_in); | |
| else if (compression_in == COMPRESSION_CCITTFAX4) | |
| TIFFSetField(faxTIFF, TIFFTAG_GROUP4OPTIONS, group4options_in); | |
| resX = 204; | |
| if (!stretch) { | |
| TIFFGetField(faxTIFF, TIFFTAG_YRESOLUTION, &resY); | |
| } else { | |
| resY = 196; | |
| } | |
| // decode the raw fax data | |
| rows = copyFaxFile(io, handle, faxTIFF, xsize, stretch, memory); | |
| if(rows <= 0) throw "Error when decoding raw fax file : check the decoder options"; | |
| // allocate the output dib | |
| dib = FreeImage_Allocate(xsize, rows, 1); | |
| unsigned pitch = FreeImage_GetPitch(dib); | |
| uint32 linesize = TIFFhowmany8(xsize); | |
| // fill the bitmap structure ... | |
| // ... palette | |
| RGBQUAD *pal = FreeImage_GetPalette(dib); | |
| if(photometric_in == PHOTOMETRIC_MINISWHITE) { | |
| pal[0].rgbRed = pal[0].rgbGreen = pal[0].rgbBlue = 255; | |
| pal[1].rgbRed = pal[1].rgbGreen = pal[1].rgbBlue = 0; | |
| } else { | |
| pal[0].rgbRed = pal[0].rgbGreen = pal[0].rgbBlue = 0; | |
| pal[1].rgbRed = pal[1].rgbGreen = pal[1].rgbBlue = 255; | |
| } | |
| // ... resolution | |
| FreeImage_SetDotsPerMeterX(dib, (unsigned)(resX/0.0254000 + 0.5)); | |
| FreeImage_SetDotsPerMeterY(dib, (unsigned)(resY/0.0254000 + 0.5)); | |
| // read the decoded scanline and fill the bitmap data | |
| FreeImage_SeekMemory(memory, 0, SEEK_SET); | |
| BYTE *bits = FreeImage_GetScanLine(dib, rows - 1); | |
| for(int k = 0; k < rows; k++) { | |
| FreeImage_ReadMemory(bits, linesize, 1, memory); | |
| bits -= pitch; | |
| } | |
| // free the TIFF wrapper | |
| TIFFClose(faxTIFF); | |
| // free the memory buffer | |
| FreeImage_CloseMemory(memory); | |
| } catch(const char *message) { | |
| if(memory) FreeImage_CloseMemory(memory); | |
| if(faxTIFF) TIFFClose(faxTIFF); | |
| if(dib) FreeImage_Unload(dib); | |
| FreeImage_OutputMessageProc(s_format_id, message); | |
| return NULL; | |
| } | |
| return dib; | |
| } | |
| // ========================================================== | |
| // Init | |
| // ========================================================== | |
| void DLL_CALLCONV | |
| InitG3(Plugin *plugin, int format_id) { | |
| s_format_id = format_id; | |
| plugin->format_proc = Format; | |
| plugin->description_proc = Description; | |
| plugin->extension_proc = Extension; | |
| plugin->regexpr_proc = RegExpr; | |
| plugin->open_proc = NULL; | |
| plugin->close_proc = NULL; | |
| plugin->pagecount_proc = NULL; | |
| plugin->pagecapability_proc = NULL; | |
| plugin->load_proc = Load; | |
| plugin->save_proc = NULL; | |
| plugin->validate_proc = NULL; | |
| plugin->mime_proc = MimeType; | |
| plugin->supports_export_bpp_proc = SupportsExportDepth; | |
| plugin->supports_export_type_proc = NULL; | |
| plugin->supports_icc_profiles_proc = NULL; | |
| } |