diff options
Diffstat (limited to 'kfaxview/libkfaximage/kfaximage.cpp')
-rw-r--r-- | kfaxview/libkfaximage/kfaximage.cpp | 667 |
1 files changed, 667 insertions, 0 deletions
diff --git a/kfaxview/libkfaximage/kfaximage.cpp b/kfaxview/libkfaximage/kfaximage.cpp new file mode 100644 index 00000000..28744923 --- /dev/null +++ b/kfaxview/libkfaximage/kfaximage.cpp @@ -0,0 +1,667 @@ +/* + 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 <qimage.h> +#include <qfile.h> + +#include <kglobal.h> +#include <klocale.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 QString &filename, QObject *parent, const char *name ) + : QObject(parent,name) +{ + KGlobal::locale()->insertCatalogue( QString::fromLatin1("libkfaximage") ); + loadImage(filename); +} + +KFaxImage::~KFaxImage() +{ } + +bool KFaxImage::loadImage( const QString &filename ) +{ + reset(); + + m_filename = filename; + m_errorString = QString::null; + + 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 +} + +QImage KFaxImage::page( unsigned int pageNr ) +{ + if (pageNr >= numPages()) { + kdDebug() << "KFaxImage::page() called with invalid page number\n"; + return QImage(); + } + pagenode *pn = m_pagenodes.at(pageNr); + GetImage(pn); + return pn->image; +} + +QPoint KFaxImage::page_dpi( unsigned int pageNr ) +{ + if (pageNr >= numPages()) { + kdDebug() << "KFaxImage::page_dpi() called with invalid page number\n"; + return QPoint(0,0); + } + pagenode *pn = m_pagenodes.at(pageNr); + GetImage(pn); + return pn->dpi; +} + +QSize KFaxImage::page_size( unsigned int pageNr ) +{ + if (pageNr >= numPages()) { + kdDebug() << "KFaxImage::page_size() called with invalid page number\n"; + return QSize(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 = QSize(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 = QImage( w, h, 1, 2, QImage::systemByteOrder() ); + pn->image.setColor(0, qRgb(255,255,255)); + pn->image.setColor(1, qRgb(0,0,0)); + pn->data = (Q_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 = QImage(); + pn->data = NULL; + pn->bytes_per_line = 0; +} + +void KFaxImage::kfaxerror(const QString& 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 = QSize(1728,0); + return newnode; +} + +static t32bits +get4(unsigned char *p, QImage::Endian endian) +{ + return (endian == QImage::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, QImage::Endian endian) +{ + return (endian == QImage::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]; + QImage::Endian endian; + t32bits IFDoff; + pagenode *pn = NULL; + QString str; + + QFile 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 = QImage::LittleEndian; + else if (memcmp(header, &bigTIFF, 4) == 0) + endian = QImage::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: /* 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 = QSize(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 */ + + QFile 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 == Q_LITTLE_ENDIAN) + for (int y=pn->image.height()-1; y>=0; --y) { + Q_UINT32 *source = (Q_UINT32 *) pn->image.scanLine(y); + Q_UINT32 *dest = source; + for (int x=(pn->bytes_per_line/4)-1; x>=0; --x) { + Q_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" |