/* This file is part of KDE FAX image library Copyright (c) 2005 Helge Deller <deller@kde.org> based on Frank D. Cringle's viewfax package Copyright (C) 1990, 1995 Frank D. Cringle. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include <config.h> #include <stdlib.h> #include <tqimage.h> #include <tqfile.h> #include <tdeglobal.h> #include <tdelocale.h> #include <kdebug.h> #include "faxexpand.h" #include "kfaximage.h" static const char FAXMAGIC[] = "\000PC Research, Inc\000\000\000\000\000\000"; static const char littleTIFF[] = "\x49\x49\x2a\x00"; static const char bigTIFF[] = "\x4d\x4d\x00\x2a"; KFaxImage::KFaxImage( const TQString &filename, TQObject *parent, const char *name ) : TQObject(parent,name) { TDEGlobal::locale()->insertCatalogue( TQString::fromLatin1("libkfaximage") ); loadImage(filename); } KFaxImage::~KFaxImage() { } bool KFaxImage::loadImage( const TQString &filename ) { reset(); m_filename = filename; m_errorString = TQString(); if (m_filename.isEmpty()) return false; int ok = notetiff(); if (!ok) { reset(); } return ok == 1; } void KFaxImage::reset() { fax_init_tables(); m_pagenodes.setAutoDelete(true); m_pagenodes.clear(); // do not reset m_errorString and m_filename, since // they may be needed by calling application } TQImage KFaxImage::page( unsigned int pageNr ) { if (pageNr >= numPages()) { kdDebug() << "KFaxImage::page() called with invalid page number\n"; return TQImage(); } pagenode *pn = m_pagenodes.at(pageNr); GetImage(pn); return pn->image; } TQPoint KFaxImage::page_dpi( unsigned int pageNr ) { if (pageNr >= numPages()) { kdDebug() << "KFaxImage::page_dpi() called with invalid page number\n"; return TQPoint(0,0); } pagenode *pn = m_pagenodes.at(pageNr); GetImage(pn); return pn->dpi; } TQSize KFaxImage::page_size( unsigned int pageNr ) { if (pageNr >= numPages()) { kdDebug() << "KFaxImage::page_size() called with invalid page number\n"; return TQSize(0,0); } pagenode *pn = m_pagenodes.at(pageNr); GetImage(pn); return pn->size; } pagenode *KFaxImage::AppendImageNode(int type) { pagenode *pn = new pagenode(); if (pn) { pn->type = type; pn->expander = g31expand; pn->strips = NULL; pn->size = TQSize(1728,2339); pn->vres = -1; pn->dpi = KFAX_DPI_FINE; m_pagenodes.append(pn); } return pn; } bool KFaxImage::NewImage(pagenode *pn, int w, int h) { pn->image = TQImage( w, h, 1, 2, TQImage::systemByteOrder() ); pn->image.setColor(0, tqRgb(255,255,255)); pn->image.setColor(1, tqRgb(0,0,0)); pn->data = (TQ_UINT16*) pn->image.bits(); pn->bytes_per_line = pn->image.bytesPerLine(); pn->dpi = KFAX_DPI_FINE; return !pn->image.isNull(); } void KFaxImage::FreeImage(pagenode *pn) { pn->image = TQImage(); pn->data = NULL; pn->bytes_per_line = 0; } void KFaxImage::kfaxerror(const TQString& error) { m_errorString = error; kdError() << "kfaxerror: " << error << endl; } /* Enter an argument in the linked list of pages */ pagenode * KFaxImage::notefile(int type) { pagenode *newnode = new pagenode(); newnode->type = type; newnode->size = TQSize(1728,0); return newnode; } static t32bits get4(unsigned char *p, TQImage::Endian endian) { return (endian == TQImage::BigEndian) ? (p[0]<<24)|(p[1]<<16)|(p[2]<<8)|p[3] : p[0]|(p[1]<<8)|(p[2]<<16)|(p[3]<<24); } static int get2(unsigned char *p, TQImage::Endian endian) { return (endian == TQImage::BigEndian) ? (p[0]<<8)|p[1] : p[0]|(p[1]<<8); } /* generate pagenodes for the images in a tiff file */ int KFaxImage::notetiff() { #define SC(x) (char *)(x) unsigned char header[8]; TQImage::Endian endian; t32bits IFDoff; pagenode *pn = NULL; TQString str; TQFile file(filename()); if (!file.open(IO_ReadOnly)) { kfaxerror(i18n("Unable to open file for reading.")); return 0; } if (file.readBlock(SC(header), 8) != 8) { kfaxerror(i18n("Unable to read file header (file too short).")); return 0; } if (memcmp(header, &littleTIFF, 4) == 0) endian = TQImage::LittleEndian; else if (memcmp(header, &bigTIFF, 4) == 0) endian = TQImage::BigEndian; else { maybe_RAW_FAX: kfaxerror(i18n("This is not a TIFF FAX file.")); // AppendImageNode(FAX_RAW); return 0; } IFDoff = get4(header+4, endian); if (IFDoff & 1) { goto maybe_RAW_FAX; } do { /* for each page */ unsigned char buf[8]; unsigned char *dir = NULL , *dp = NULL; int ndirent; pixnum iwidth = 1728; pixnum iheight = 2339; int inverse = false; int lsbfirst = 0; int t4opt = 0, comp = 0; int orient = TURN_NONE; int yres = 196; /* 98.0 */ struct strip *strips = NULL; unsigned int rowsperstrip = 0; t32bits nstrips = 1; if (!file.at(IFDoff)) { realbad: kfaxerror( i18n("Invalid or incomplete TIFF file.") ); bad: if (strips) free(strips); if (dir) free(dir); file.close(); return 1; } if (file.readBlock(SC(buf), 2) != 2) goto realbad; ndirent = get2(buf, endian); int len = 12*ndirent+4; dir = (unsigned char *) malloc(len); if (file.readBlock(SC(dir), len) != len) goto realbad; for (dp = dir; ndirent; ndirent--, dp += 12) { /* for each directory entry */ int tag, ftype; t32bits count, value = 0; tag = get2(dp, endian); ftype = get2(dp+2, endian); count = get4(dp+4, endian); switch(ftype) { /* value is offset to list if count*size > 4 */ case 3: /* short */ value = get2(dp+8, endian); break; case 4: /* long */ value = get4(dp+8, endian); break; case 5: /* offset to rational */ value = get4(dp+8, endian); break; } switch(tag) { case 256: /* ImageWidth */ iwidth = value; break; case 257: /* ImageLength */ iheight = value; break; case 259: /* Compression */ comp = value; break; case 262: /* PhotometricInterpretation */ inverse ^= (value == 1); break; case 266: /* FillOrder */ lsbfirst = (value == 2); break; case 273: /* StripOffsets */ nstrips = count; strips = (struct strip *) malloc(count * sizeof *strips); if (count == 1 || (count == 2 && ftype == 3)) { strips[0].offset = value; if (count == 2) strips[1].offset = get2(dp+10, endian); break; } if (!file.at(value)) goto realbad; for (count = 0; count < nstrips; count++) { if (file.readBlock(SC(buf), (ftype == 3) ? 2 : 4) <= 0) goto realbad; strips[count].offset = (ftype == 3) ? get2(buf, endian) : get4(buf, endian); } break; case 274: /* Qt::Orientation */ switch(value) { default: /* row0 at top, col0 at left */ orient = 0; break; case 2: /* row0 at top, col0 at right */ orient = TURN_M; break; case 3: /* row0 at bottom, col0 at right */ orient = TURN_U; break; case 4: /* row0 at bottom, col0 at left */ orient = TURN_U|TURN_M; break; case 5: /* row0 at left, col0 at top */ orient = TURN_M|TURN_L; break; case 6: /* row0 at right, col0 at top */ orient = TURN_U|TURN_L; break; case 7: /* row0 at right, col0 at bottom */ orient = TURN_U|TURN_M|TURN_L; break; case 8: /* row0 at left, col0 at bottom */ orient = TURN_L; break; } break; case 278: /* RowsPerStrip */ rowsperstrip = value; break; case 279: /* StripByteCounts */ if (count != nstrips) { str = i18n("In file %1\nStripsPerImage tag 273=%2,tag279=%3\n") .arg(filename()).arg(nstrips).arg(count); kfaxerror(str); goto realbad; } if (count == 1 || (count == 2 && ftype == 3)) { strips[0].size = value; if (count == 2) strips[1].size = get2(dp+10, endian); break; } if (!file.at(value)) goto realbad; for (count = 0; count < nstrips; count++) { if (file.readBlock(SC(buf), (ftype == 3) ? 2 : 4) <= 0) goto realbad; strips[count].size = (ftype == 3) ? get2(buf, endian) : get4(buf, endian); } break; case 283: /* YResolution */ if (!file.at(value) || file.readBlock(SC(buf), 8) != 8) goto realbad; yres = get4(buf, endian) / get4(buf+4, endian); break; case 292: /* T4Options */ t4opt = value; break; case 293: /* T6Options */ /* later */ break; case 296: /* ResolutionUnit */ if (value == 3) yres = (yres * 254) / 100; /* *2.54 */ break; } } IFDoff = get4(dp, endian); free(dir); dir = NULL; if (comp == 5) { // compression type 5 is LZW compression // XXX kfaxerror(i18n("Due to patent reasons LZW (Lempel-Ziv & Welch) " "compressed Fax files cannot be loaded yet.\n")); goto bad; } if (comp < 2 || comp > 4) { kfaxerror(i18n("This version can only handle Fax files\n")); goto bad; } pn = AppendImageNode(FAX_TIFF); pn->nstrips = nstrips; pn->rowsperstrip = nstrips > 1 ? rowsperstrip : iheight; pn->strips = strips; pn->size = TQSize(iwidth,iheight); pn->inverse = inverse; pn->lsbfirst = lsbfirst; pn->orient = orient; pn->dpi.setY(yres); pn->vres = (yres > 150) ? 1:0; /* arbitrary threshold for fine resolution */ if (comp == 2) pn->expander = MHexpand; else if (comp == 3) pn->expander = (t4opt & 1) ? g32expand : g31expand; else pn->expander = g4expand; } while (IFDoff); file.close(); return 1; #undef UC } /* report error and remove bad file from the list */ void KFaxImage::badfile(pagenode *pn) { kfaxerror(i18n("%1: Bad Fax File").arg(filename())); FreeImage(pn); } /* rearrange input bits into t16bits lsb-first chunks */ static void normalize(pagenode *pn, int revbits, int swapbytes, size_t length) { t32bits *p = (t32bits *) pn->data; kdDebug() << "normalize = " << ((revbits<<1)|swapbytes) << endl; switch ((revbits<<1)|swapbytes) { case 0: break; case 1: for ( ; length; length -= 4) { t32bits t = *p; *p++ = ((t & 0xff00ff00) >> 8) | ((t & 0x00ff00ff) << 8); } break; case 2: for ( ; length; length -= 4) { t32bits t = *p; t = ((t & 0xf0f0f0f0) >> 4) | ((t & 0x0f0f0f0f) << 4); t = ((t & 0xcccccccc) >> 2) | ((t & 0x33333333) << 2); *p++ = ((t & 0xaaaaaaaa) >> 1) | ((t & 0x55555555) << 1); } break; case 3: for ( ; length; length -= 4) { t32bits t = *p; t = ((t & 0xff00ff00) >> 8) | ((t & 0x00ff00ff) << 8); t = ((t & 0xf0f0f0f0) >> 4) | ((t & 0x0f0f0f0f) << 4); t = ((t & 0xcccccccc) >> 2) | ((t & 0x33333333) << 2); *p++ = ((t & 0xaaaaaaaa) >> 1) | ((t & 0x55555555) << 1); } } } /* get compressed data into memory */ unsigned char * KFaxImage::getstrip(pagenode *pn, int strip) { size_t offset, roundup; unsigned char *Data; union { t16bits s; unsigned char b[2]; } so; #define ShortOrder so.b[1] so.s = 1; /* XXX */ TQFile file(filename()); if (!file.open(IO_ReadOnly)) { badfile(pn); return NULL; } if (pn->strips == NULL) { offset = 0; pn->length = file.size(); } else if (strip < pn->nstrips) { offset = pn->strips[strip].offset; pn->length = pn->strips[strip].size; } else { kfaxerror( i18n("Trying to expand too many strips.") ); return NULL; } /* round size to full boundary plus t32bits */ roundup = (pn->length + 7) & ~3; Data = (unsigned char *) malloc(roundup); /* clear the last 2 t32bits, to force the expander to terminate even if the file ends in the middle of a fax line */ *((t32bits *) Data + roundup/4 - 2) = 0; *((t32bits *) Data + roundup/4 - 1) = 0; /* we expect to get it in one gulp... */ if (!file.at(offset) || (size_t) file.readBlock((char *)Data, pn->length) != pn->length) { badfile(pn); free(Data); return NULL; } file.close(); pn->data = (t16bits *) Data; if (pn->strips == NULL && memcmp(Data, FAXMAGIC, sizeof(FAXMAGIC)-1) == 0) { /* handle ghostscript / PC Research fax file */ if (Data[24] != 1 || Data[25] != 0){ kfaxerror( i18n("Only the first page of the PC Research multipage file will be shown.") ); } pn->length -= 64; pn->vres = Data[29]; pn->data += 32; roundup -= 64; } normalize(pn, !pn->lsbfirst, ShortOrder, roundup); if (pn->size.height() == 0) pn->size.setHeight( G3count(pn, pn->expander == g32expand) ); if (pn->size.height() == 0) { kfaxerror( i18n("No fax found in file.") ); badfile(pn); free(Data); return NULL; } if (pn->strips == NULL) pn->rowsperstrip = pn->size.height(); return Data; } static void drawline(pixnum *run, int LineNum, pagenode *pn) { t32bits *p, *p1; /* p - current line, p1 - low-res duplicate */ pixnum *r; /* pointer to run-lengths */ t32bits pix; /* current pixel value */ t32bits acc; /* pixel accumulator */ int nacc; /* number of valid bits in acc */ int tot; /* total pixels in line */ int n; LineNum += pn->stripnum * pn->rowsperstrip; if (LineNum >= pn->size.height()) { if (LineNum == pn->size.height()) kdError() << "Height exceeded\n"; return; } p = (t32bits *) pn->image.scanLine(LineNum*(2-pn->vres)); p1 =(t32bits *)( pn->vres ? 0 : pn->image.scanLine(1+LineNum*(2-pn->vres))); r = run; acc = 0; nacc = 0; pix = pn->inverse ? ~0 : 0; tot = 0; while (tot < pn->size.width()) { n = *r++; tot += n; /* Watch out for buffer overruns, e.g. when n == 65535. */ if (tot > pn->size.width()) break; if (pix) acc |= (~(t32bits)0 >> nacc); else if (nacc) acc &= (~(t32bits)0 << (32 - nacc)); else acc = 0; if (nacc + n < 32) { nacc += n; pix = ~pix; continue; } *p++ = acc; if (p1) *p1++ = acc; n -= 32 - nacc; while (n >= 32) { n -= 32; *p++ = pix; if (p1) *p1++ = pix; } acc = pix; nacc = n; pix = ~pix; } if (nacc) { *p++ = acc; if (p1) *p1++ = acc; } } int KFaxImage::GetPartImage(pagenode *pn, int n) { unsigned char *Data = getstrip(pn, n); if (Data == 0) return 3; pn->stripnum = n; (*pn->expander)(pn, drawline); free(Data); return 1; } int KFaxImage::GetImage(pagenode *pn) { if (!pn->image.isNull()) return 1; int i; if (pn->strips == 0) { kdDebug() << "Loading RAW fax file " << m_filename << " size=" << pn->size << endl; /* raw file; maybe we don't have the height yet */ unsigned char *Data = getstrip(pn, 0); if (Data == 0){ return 0; } if (!NewImage(pn, pn->size.width(), (pn->vres ? 1:2) * pn->size.height())) return 0; (*pn->expander)(pn, drawline); } else { /* multi-strip tiff */ kdDebug() << "Loading MULTI STRIP TIFF fax file " << m_filename << endl; if (!NewImage(pn, pn->size.width(), (pn->vres ? 1:2) * pn->size.height())) return 0; pn->stripnum = 0; kdDebug() << "has " << pn->nstrips << " strips.\n"; for (i = 0; i < pn->nstrips; i++){ int k = GetPartImage(pn, i); if ( k == 3 ){ FreeImage(pn); kfaxerror( i18n("Fax G3 format not yet supported.") ); return k; } } } // byte-swapping the image on little endian machines #if defined(Q_BYTE_ORDER) && (Q_BYTE_ORDER == TQ_LITTLE_ENDIAN) for (int y=pn->image.height()-1; y>=0; --y) { TQ_UINT32 *source = (TQ_UINT32 *) pn->image.scanLine(y); TQ_UINT32 *dest = source; for (int x=(pn->bytes_per_line/4)-1; x>=0; --x) { TQ_UINT32 dv = 0, sv = *source; for (int bit=32; bit>0; --bit) { dv <<= 1; dv |= sv&1; sv >>= 1; } *dest = dv; ++dest; ++source; } } #endif kdDebug() << filename() << "\n\tsize = " << pn->size << "\n\tDPI = " << pn->dpi << "\n\tresolution = " << (pn->vres ? "fine" : "normal") << endl; return 1; } #include "kfaximage.moc"