You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
669 lines
16 KiB
669 lines
16 KiB
/* |
|
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 "faxexpand.h" |
|
#include "kfaximage.h" |
|
#include "kdebug.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) |
|
{ |
|
setObjectName(QLatin1String(name)); |
|
KGlobal::locale()->insertCatalog( 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(); |
|
qDeleteAll(m_pagenodes); |
|
m_pagenodes.clear(); |
|
// do not reset m_errorString and m_filename, since |
|
// they may be needed by calling application |
|
} |
|
|
|
QImage KFaxImage::page( int pageNr ) |
|
{ |
|
if (pageNr < 0 || pageNr >= numPages()) { |
|
kDebug() << "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( int pageNr ) |
|
{ |
|
if (pageNr < 0 || pageNr >= numPages()) { |
|
kDebug() << "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( int pageNr ) |
|
{ |
|
if (pageNr < 0 || pageNr >= numPages()) { |
|
kDebug() << "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 = (quint16*) 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; |
|
kError() << "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(QIODevice::ReadOnly)) { |
|
kfaxerror(i18n("Unable to open file for reading.")); |
|
return 0; |
|
} |
|
|
|
if (file.read(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.seek(IFDoff)) { |
|
realbad: |
|
kfaxerror( i18n("Invalid or incomplete TIFF file.") ); |
|
bad: |
|
if (strips) |
|
free(strips); |
|
if (dir) |
|
free(dir); |
|
file.close(); |
|
return 1; |
|
} |
|
if (file.read(SC(buf), 2) != 2) |
|
goto realbad; |
|
ndirent = get2(buf, endian); |
|
int len = 12*ndirent+4; |
|
dir = (unsigned char *) malloc(len); |
|
if (file.read(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.seek(value)) |
|
goto realbad; |
|
for (count = 0; count < nstrips; count++) { |
|
if (file.read(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", |
|
filename(), nstrips, 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.seek(value)) |
|
goto realbad; |
|
for (count = 0; count < nstrips; count++) { |
|
if (file.read(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.seek(value) || |
|
file.read(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", 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; |
|
|
|
kDebug() << "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(QIODevice::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.seek(offset) || |
|
(size_t) file.read((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()) |
|
kError() << "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) { |
|
|
|
kDebug() << "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 */ |
|
kDebug() << "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; |
|
kDebug() << "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) { |
|
quint32 *source = (quint32 *) pn->image.scanLine(y); |
|
quint32 *dest = source; |
|
for (int x=(pn->bytes_per_line/4)-1; x>=0; --x) { |
|
quint32 dv = 0, sv = *source; |
|
for (int bit=32; bit>0; --bit) { |
|
dv <<= 1; |
|
dv |= sv&1; |
|
sv >>= 1; |
|
} |
|
*dest = dv; |
|
++dest; |
|
++source; |
|
} |
|
} |
|
#endif |
|
|
|
kDebug() << filename() |
|
<< "\n\tsize = " << pn->size |
|
<< "\n\tDPI = " << pn->dpi |
|
<< "\n\tresolution = " << (pn->vres ? "fine" : "normal") |
|
<< endl; |
|
|
|
return 1; |
|
} |
|
|
|
|
|
#include "kfaximage.moc" |
|
|
|
|