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.
tdegraphics/kfaxview/libkfaximage/kfaximage.cpp

668 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 <tqimage.h>
#include <tqfile.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 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"