summaryrefslogtreecommitdiffstats
path: root/kfaxview/libkfaximage/kfaximage.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'kfaxview/libkfaximage/kfaximage.cpp')
-rw-r--r--kfaxview/libkfaximage/kfaximage.cpp667
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 <[email protected]>
+
+ 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"