Bug 131263 - kfaxview lowres scaling issue/no LZW compression
Summary: kfaxview lowres scaling issue/no LZW compression
Status: RESOLVED FIXED
Alias: None
Product: kviewshell
Classification: Applications
Component: general (show other bugs)
Version: unspecified
Platform: Unlisted Binaries Linux
: NOR normal
Target Milestone: ---
Assignee: Matthias Hoelzer-Kluepfel
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2006-07-23 23:36 UTC by Hans-Peter Jansen
Modified: 2006-10-28 18:48 UTC (History)
3 users (show)

See Also:
Latest Commit:
Version Fixed In:


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Hans-Peter Jansen 2006-07-23 23:36:29 UTC
Version:           3.5.3 (using KDE KDE 3.5.3)
Installed from:    Unspecified Linux
OS:                Linux

Lowres faxes (98 dpi vert. resolution) are displayed vertically queezed, similar to the behavior kimgio tiff module displays them. While this is bad for the latter, it's unacceptable for a decent fax viewer.

While at it, there are issues with LZW compressed fax images, like this one:
~> tiffinfo gavi.TIF 
TIFF Directory at offset 0x6ffe (28670)
  Subfile Type: (0 = 0x0)
  Image Width: 1728 Image Length: 2272
  Resolution: 200, 200 pixels/inch
  Bits/Sample: 1
  Compression Scheme: LZW
  Photometric Interpretation: min-is-black
  Thresholding: bilevel art scan
  Samples/Pixel: 1
  Rows/Strip: 56
  Planar Configuration: single image plane
  Predictor: none 1 (0x1)

kfaxview displays an error, when trying to dplay this fax:
Due to patent reasons LZW (Lempel-Ziv & Welch) compressed Fax files 
cannot be loaded yet. :-(.

All apps, which base their tiff decoding on libtiff, like konqueror(kimgio) or qfaxreader[.sf.net] get it displayed fine, but kfaxview bases its decoding on code from an app called faxview, which was last touched 1995. Indeed, up to 2004, there were patent claims from unisys for LZW. 

The reason, why I would like to get this fixed is, that kimgio is able to display the first page from a fax file only, while qfaxreader has currently issues with printing :-(..
Comment 1 Wilfried Huss 2006-08-28 16:05:04 UTC
SVN commit 578144 by whuss:

Support LZW compressed fax files.
This adds a dependency to libtiff.

BUG: 131263

 M  +5 -1      Makefile.am  
 A             configure.in.bot  
 M  +2 -2      fax/Makefile.am  
 A             fax/configure.in.in  
 M  +82 -37    fax/faxrenderer.cpp  
 M  +6 -5      fax/faxrenderer.h  


--- branches/work/kviewshell-0.7/kviewshell/plugins/Makefile.am #578143:578144
@@ -2,4 +2,8 @@
 KVS_PDF_SUBDIR=pdf
 endif
 
-SUBDIRS = djvu dvi fax $(KVS_PDF_SUBDIR) ps
+if include_TIFF
+KVS_FAX_SUBDIR=fax
+endif
+
+SUBDIRS = djvu dvi $(KVS_FAX_SUBDIR) $(KVS_PDF_SUBDIR) ps
--- branches/work/kviewshell-0.7/kviewshell/plugins/fax/Makefile.am #578143:578144
@@ -17,11 +17,11 @@
 kde_module_LTLIBRARIES = kfaxviewpart.la
 kfaxviewpart_la_LDFLAGS = $(all_libraries) $(KDE_PLUGIN) -module
 kfaxviewpart_la_LIBADD = -lkdeprint -lkparts $(top_builddir)/kviewshell/shell/libkmultipage.la \
-	libkfaximage/libkfaximage.la
+	libkfaximage/libkfaximage.la $(LIBTIFF)
 kfaxviewpart_la_SOURCES = faxmultipage.cpp faxrenderer.cpp faxprintsettings.cpp
 
 pluginsdir = $(kde_datadir)
-#plugins_DATA = 
+#plugins_DATA =
 
 partdesktopdir = $(kde_servicesdir)
 partdesktop_DATA = kvs_fax_part.desktop
--- branches/work/kviewshell-0.7/kviewshell/plugins/fax/faxrenderer.cpp #578143:578144
@@ -33,7 +33,7 @@
 //#define KF_DEBUG
 
 FaxRenderer::FaxRenderer(KMultiPage* _multiPage)
-  : DocumentRenderer(_multiPage)
+  : DocumentRenderer(_multiPage), fax(0)
 {
 #ifdef KF_DEBUG
   kdError(kvs::fax) << "FaxRenderer( parent=" << par << " )" << endl;
@@ -79,7 +79,9 @@
 
   double resolution = id.resolution;
 
-  QImage img = fax.page(id.pageNumber - 1);
+  QImage img = getRawImage(id.pageNumber);
+  if (img.isNull())
+    return 0;
 
   SimplePageSize psize = pageSizes[id.pageNumber - 1];
   if (psize.isValid())
@@ -123,11 +125,17 @@
   QMutexLocker locker(&mutex);
 
   // If fname is the empty string, then this means: "close".
-  if (fname.isEmpty()) {
+  if (fname.isEmpty())
+  {
+    if (fax)
+    {
+      TIFFClose(fax);
+      fax = 0;
+    }
     kdDebug(kvs::fax) << "FaxRenderer::setFile( ... ) called with empty filename. Closing the file." << endl;
     return true;
   }
-  
+
   // Paranoid saftey checks: make sure the file actually exists, and
   // that it is a file, not a directory. Otherwise, show an error
   // message and exit..
@@ -143,51 +151,44 @@
 
   // Now we assume that the file is fine and load the file into the
   // fax member. We abort on error and give an error message.
-  bool ok = fax.loadImage(filename);
+  fax = TIFFOpen(QFile::encodeName( filename ), "r");
+  if (!fax)
+  {
+    KMessageBox::error(parentWidget,
+      i18n("<qt><strong>File error.</strong> The specified file '%1' could not be loaded.</qt>").arg(filename),
+      i18n("File Error"));
 
-  // It can happen that fax.loadImage() returns with 'ok == true', but
-  // still the file could NOT be loaded. This happens, e.g. for TIFF
-  // file that do NOT contain FAX, but other image formats. We handle
-  // that case here also.
-  if ( (!ok) || (fax.numPages() == 0)) {
-    // Unfortunately, it can happen that fax.loadImage() fails WITHOUT
-    // leaving an error message in fax.errorString(). We try to handle
-    // this case gracefully.
-    if (fax.errorString().isEmpty())
-      KMessageBox::error( parentWidget,
-			  i18n("<qt><strong>File error.</strong> The specified file '%1' could not be loaded.</qt>").arg(filename),
-			  i18n("File Error"));
-    else
-      KMessageBox::detailedError( parentWidget,
-				  i18n("<qt><strong>File error.</strong> The specified file '%1' could not be loaded.</qt>").arg(filename),
-				  fax.errorString(),
-				  i18n("File Error"));
     clear();
     return false;
   }
 
   // Set the number of pages page sizes
-  numPages = fax.numPages();
+  tdir_t dirs = TIFFNumberOfDirectories(fax);
+  numPages = dirs;
 
   // Set the page size for the first page in the pageSizes array.
   // The rest of the page sizes will be calculated on demand by the drawPage function.
   pageSizes.resize(numPages);
   Length w,h;
 
-  if (numPages != 0) {
-    for(Q_UINT16 pg=0; pg < numPages; pg++) {
-      QSize pageSize = fax.page_size(pg);
-      QPoint dpi = fax.page_dpi(pg);
-      double dpix = dpi.x();
-      double dpiy = dpi.y();
+  if (numPages != 0)
+  {
+    for(tdir_t pg = 0; pg < dirs; pg++)
+    {
+      if (!TIFFSetDirectory(fax, pg))
+        continue;
 
-      if (dpix*dpiy < 1.0) {
-        kdError(kvs::fax) << "File invalid resolutions, dpi x = " << dpix << ", dpi y = "  << dpiy << ". This information will be ignored and 75 DPI assumed." << endl;
-        dpix = dpiy = 75.0;
-      }
+      QPoint dpi = getDPI(pg + 1);
 
-      w.setLength_in_inch(pageSize.width() / dpix);
-      h.setLength_in_inch(pageSize.height() / dpiy);
+      Q_UINT32 width = 0;
+      Q_UINT32 height = 0;
+
+      if (TIFFGetField(fax, TIFFTAG_IMAGEWIDTH, &width) != 1 ||
+          TIFFGetField(fax, TIFFTAG_IMAGELENGTH, &height) != 1 )
+        continue;
+
+      w.setLength_in_inch(width / (double)dpi.x());
+      h.setLength_in_inch(height / (double)dpi.y());
       pageSizes[pg].setPageSize(w, h);
     }
   }
@@ -199,13 +200,57 @@
 
 QImage FaxRenderer::getRawImage(PageNumber page)
 {
-  return fax.page(page - 1);
+  if (!TIFFSetDirectory(fax, page - 1))
+    return QImage();
+
+  Q_UINT32 width = 0;
+  Q_UINT32 height = 0;
+
+  if (TIFFGetField(fax, TIFFTAG_IMAGEWIDTH, &width) != 1 ||
+      TIFFGetField(fax, TIFFTAG_IMAGELENGTH, &height) != 1 )
+    return QImage();
+
+  QImage img(width, height, 32);
+  Q_UINT32* data = (Q_UINT32*)img.bits();
+
+  if (TIFFReadRGBAImageOriented(fax, width, height, data, ORIENTATION_TOPLEFT) != 0)
+  {
+    Q_UINT32 size = width * height;
+    for (Q_UINT32 i = 0; i < size; ++i)
+    {
+      Q_UINT32 red = (data[i] & 0x00FF0000) >> 16;
+      Q_UINT32 blue = (data[i] & 0x000000FF) << 16;
+      data[i] = (data[i] & 0xFF00FF00) + red + blue;
+    }
+  }
+  else
+  {
+    return QImage();
+  }
+
+  return img;
 }
 
 
 QPoint FaxRenderer::getDPI(PageNumber page)
 {
-  return fax.page_dpi(page - 1);
+  if (!TIFFSetDirectory(fax, page-1))
+    return QPoint(0, 0);
+
+  float dpix = 0.0;
+  float dpiy = 0.0;
+  if (TIFFGetField(fax, TIFFTAG_XRESOLUTION, &dpix) != 1 ||
+      TIFFGetField(fax, TIFFTAG_YRESOLUTION, &dpiy) != 1)
+    return QPoint(0, 0);
+
+  kdDebug(kvs::fax) << "resolutions, dpi x = " << dpix << ", dpi y = "  << dpiy << "." << endl;
+
+  if (dpix <= 1 || dpiy <= 1) {
+    kdError(kvs::fax) << "File invalid resolutions, dpi x = " << dpix << ", dpi y = "  << dpiy << ". This information will be ignored and 75 DPI assumed." << endl;
+    dpix = dpiy = 75;
+  }
+
+  return QPoint((int)dpix, (int)dpiy);
 }
 
 #include "faxrenderer.moc"
--- branches/work/kviewshell-0.7/kviewshell/plugins/fax/faxrenderer.h #578143:578144
@@ -23,12 +23,13 @@
 
 
 #include "documentRenderer.h"
-#include "kfaximage.h"
 
+#include <tiffio.h>
+
 class documentPage;
 
 /*! \brief Well-documented minimal implementation of a documentRenderer for reading FAX files
-  
+
 This class provides a well-documented reference implementation of a
 documentRenderer, suitable as a starting point for a real-world
 implementation. This class is responsible for document loading and
@@ -65,7 +66,7 @@
       is contained in the class "KFaxImage", to keep this reference
       implementation short.
 
-      @param fname the name of the file that should be opened. 
+      @param fname the name of the file that should be opened.
   */
   virtual bool setFile(const QString& fname, const KURL &);
 
@@ -84,8 +85,8 @@
   QPoint getDPI(PageNumber page);
 
 private:
-  /** This class holds the fax file */
-  KFaxImage fax;
+  /** This pointer holds the fax file */
+  TIFF* fax;
 };
 
 #endif
Comment 2 Wilfried Huss 2006-10-28 18:48:15 UTC
SVN commit 599807 by whuss:

port of commit 578144:

Support LZW compressed fax files.
This adds a dependency to libtiff.

CCBUG: 131263

 M  +7 -1      CMakeLists.txt  
 M  +2 -4      fax/CMakeLists.txt  
 M  +91 -46    fax/faxrenderer.cpp  
 M  +6 -5      fax/faxrenderer.h  


--- trunk/KDE/kdegraphics/kviewshell/plugins/CMakeLists.txt #599806:599807
@@ -9,8 +9,14 @@
 if(FreeType2_FOUND)
    add_subdirectory( dvi ) 
 endif(FreeType2_FOUND)
-add_subdirectory( fax )
 
+macro_optional_find_package(TIFF)
+if(TIFF_FOUND)
+   add_subdirectory( fax )
+else(TIFF_FOUND)
+   message(STATUS "libtiff was not found. The KViewShell FAX-plugin will not be build.")
+endif(TIFF_FOUND)
+
 macro_optional_find_package(PopplerQt4)
 if (POPPLER_QT4_FOUND)
   add_subdirectory( pdf )
--- trunk/KDE/kdegraphics/kviewshell/plugins/fax/CMakeLists.txt #599806:599807
@@ -1,9 +1,7 @@
 
-add_subdirectory( libkfaximage ) 
+include_directories( ${CMAKE_SOURCE_DIR}/kviewshell/shell )
 
-include_directories( ${CMAKE_SOURCE_DIR}/kviewshell/shell ${CMAKE_CURRENT_SOURCE_DIR}/libkfaximage  )
 
-
 ########### next target ###############
 
 set(kfaxviewpart_PART_SRCS faxmultipage.cpp faxrenderer.cpp faxprintsettings.cpp )
@@ -14,7 +12,7 @@
 
 kde4_install_libtool_file( ${PLUGIN_INSTALL_DIR} kfaxviewpart )
 
-target_link_libraries(kfaxviewpart ${KDE4_KDEPRINT_LIBS} ${KDE4_KPARTS_LIBS} kmultipage kfaximage )
+target_link_libraries(kfaxviewpart ${KDE4_KDEPRINT_LIBS} ${KDE4_KPARTS_LIBS} ${TIFF_LIBRARIES} kmultipage )
 
 install(TARGETS kfaxviewpart  DESTINATION ${PLUGIN_INSTALL_DIR} )
 
--- trunk/KDE/kdegraphics/kviewshell/plugins/fax/faxrenderer.cpp #599806:599807
@@ -33,7 +33,7 @@
 //#define KF_DEBUG
 
 FaxRenderer::FaxRenderer(KMultiPage* _multiPage)
-  : DocumentRenderer(_multiPage)
+  : DocumentRenderer(_multiPage), fax(0)
 {
 #ifdef KF_DEBUG
   kError(kvs::fax) << "FaxRenderer( parent=" << par << " )" << endl;
@@ -79,7 +79,9 @@
 
   double resolution = id.resolution;
 
-  QImage img = fax.page(id.pageNumber - 1);
+  QImage img = getRawImage(id.pageNumber);
+  if (img.isNull())
+    return 0;
 
   SimplePageSize psize = pageSizes[id.pageNumber - 1];
   if (psize.isValid())
@@ -113,17 +115,6 @@
 }
 
 
-QImage FaxRenderer::getRawImage(PageNumber page)
-{
-  return fax.page(page - 1);
-}
-
-QPoint FaxRenderer::getDPI(PageNumber page)
-{
-  return fax.page_dpi(page - 1);
-}
-
-
 bool FaxRenderer::setFile(const QString &fname, const KUrl &)
 {
 #ifdef KF_DEBUG
@@ -134,11 +125,17 @@
   QMutexLocker locker(&mutex);
 
   // If fname is the empty string, then this means: "close".
-  if (fname.isEmpty()) {
+  if (fname.isEmpty())
+  {
+    if (fax)
+    {
+      TIFFClose(fax);
+      fax = 0;
+    }
     kDebug(kvs::fax) << "FaxRenderer::setFile( ... ) called with empty filename. Closing the file." << endl;
     return true;
   }
-  
+
   // Paranoid saftey checks: make sure the file actually exists, and
   // that it is a file, not a directory. Otherwise, show an error
   // message and exit..
@@ -154,51 +151,44 @@
 
   // Now we assume that the file is fine and load the file into the
   // fax member. We abort on error and give an error message.
-  bool ok = fax.loadImage(filename);
+  fax = TIFFOpen(QFile::encodeName( filename ), "r");
+  if (!fax)
+  {
+    KMessageBox::error(parentWidget,
+      i18n("<qt><strong>File error.</strong> The specified file '%1' could not be loaded.</qt>").arg(filename),
+      i18n("File Error"));
 
-  // It can happen that fax.loadImage() returns with 'ok == true', but
-  // still the file could NOT be loaded. This happens, e.g. for TIFF
-  // file that do NOT contain FAX, but other image formats. We handle
-  // that case here also.
-  if ( (!ok) || (fax.numPages() == 0)) {
-    // Unfortunately, it can happen that fax.loadImage() fails WITHOUT
-    // leaving an error message in fax.errorString(). We try to handle
-    // this case gracefully.
-    if (fax.errorString().isEmpty())
-      KMessageBox::error( parentWidget,
-			  i18n("<qt><strong>File error.</strong> The specified file '%1' could not be loaded.</qt>", filename),
-			  i18n("File Error"));
-    else
-      KMessageBox::detailedError( parentWidget,
-				  i18n("<qt><strong>File error.</strong> The specified file '%1' could not be loaded.</qt>", filename),
-				  fax.errorString(),
-				  i18n("File Error"));
     clear();
     return false;
   }
 
   // Set the number of pages page sizes
-  numPages = fax.numPages();
+  tdir_t dirs = TIFFNumberOfDirectories(fax);
+  numPages = dirs;
 
   // Set the page size for the first page in the pageSizes array.
   // The rest of the page sizes will be calculated on demand by the drawPage function.
   pageSizes.resize(numPages);
   Length w,h;
 
-  if (numPages != 0) {
-    for(quint16 pg=0; pg < numPages; pg++) {
-      QSize pageSize = fax.page_size(pg);
-      QPoint dpi = fax.page_dpi(pg);
-      double dpix = dpi.x();
-      double dpiy = dpi.y();
+  if (numPages != 0)
+  {
+    for(tdir_t pg = 0; pg < dirs; pg++)
+    {
+      if (!TIFFSetDirectory(fax, pg))
+        continue;
 
-      if (dpix*dpiy < 1.0) {
-        kError(kvs::fax) << "File invalid resolutions, dpi x = " << dpix << ", dpi y = "  << dpiy << ". This information will be ignored and 75 DPI assumed." << endl;
-        dpix = dpiy = 75.0;
-      }
+      QPoint dpi = getDPI(pg + 1);
 
-      w.setLength_in_inch(pageSize.width() / dpix);
-      h.setLength_in_inch(pageSize.height() / dpiy);
+      quint32 width = 0;
+      quint32 height = 0;
+
+      if (TIFFGetField(fax, TIFFTAG_IMAGEWIDTH, &width) != 1 ||
+          TIFFGetField(fax, TIFFTAG_IMAGELENGTH, &height) != 1 )
+        continue;
+
+      w.setLength_in_inch(width / (double)dpi.x());
+      h.setLength_in_inch(height / (double)dpi.y());
       pageSizes[pg].setPageSize(w, h);
     }
   }
@@ -208,4 +198,59 @@
 }
 
 
+QImage FaxRenderer::getRawImage(PageNumber page)
+{
+  if (!TIFFSetDirectory(fax, page - 1))
+    return QImage();
+
+  quint32 width = 0;
+  quint32 height = 0;
+
+  if (TIFFGetField(fax, TIFFTAG_IMAGEWIDTH, &width) != 1 ||
+      TIFFGetField(fax, TIFFTAG_IMAGELENGTH, &height) != 1 )
+    return QImage();
+
+  QImage img(width, height, 32);
+  quint32* data = (quint32*)img.bits();
+
+  if (TIFFReadRGBAImageOriented(fax, width, height, data, ORIENTATION_TOPLEFT) != 0)
+  {
+    quint32 size = width * height;
+    for (quint32 i = 0; i < size; ++i)
+    {
+      Q_UINT32 red = (data[i] & 0x00FF0000) >> 16;
+      Q_UINT32 blue = (data[i] & 0x000000FF) << 16;
+      data[i] = (data[i] & 0xFF00FF00) + red + blue;
+    }
+  }
+  else
+  {
+    return QImage();
+  }
+
+  return img;
+}
+
+
+QPoint FaxRenderer::getDPI(PageNumber page)
+{
+  if (!TIFFSetDirectory(fax, page-1))
+    return QPoint(0, 0);
+
+  float dpix = 0.0;
+  float dpiy = 0.0;
+  if (TIFFGetField(fax, TIFFTAG_XRESOLUTION, &dpix) != 1 ||
+      TIFFGetField(fax, TIFFTAG_YRESOLUTION, &dpiy) != 1)
+    return QPoint(0, 0);
+
+  kDebug(kvs::fax) << "resolutions, dpi x = " << dpix << ", dpi y = "  << dpiy << "." << endl;
+
+  if (dpix <= 1 || dpiy <= 1) {
+    kError(kvs::fax) << "File invalid resolutions, dpi x = " << dpix << ", dpi y = "  << dpiy << ". This information will be ignored and 75 DPI assumed." << endl;
+    dpix = dpiy = 75;
+  }
+
+  return QPoint((int)dpix, (int)dpiy);
+}
+
 #include "faxrenderer.moc"
--- trunk/KDE/kdegraphics/kviewshell/plugins/fax/faxrenderer.h #599806:599807
@@ -24,12 +24,13 @@
 #include <qimage.h>
 
 #include "documentRenderer.h"
-#include "kfaximage.h"
 
+#include <tiffio.h>
+
 class documentPage;
 
 /*! \brief Well-documented minimal implementation of a documentRenderer for reading FAX files
-  
+
 This class provides a well-documented reference implementation of a
 documentRenderer, suitable as a starting point for a real-world
 implementation. This class is responsible for document loading and
@@ -66,7 +67,7 @@
       is contained in the class "KFaxImage", to keep this reference
       implementation short.
 
-      @param fname the name of the file that should be opened. 
+      @param fname the name of the file that should be opened.
   */
   virtual bool setFile(const QString& fname, const KUrl &);
 
@@ -85,8 +86,8 @@
   QPoint getDPI(PageNumber page);
 
 private:
-  /** This class holds the fax file */
-  KFaxImage fax;
+  /** This pointer holds the fax file */
+  TIFF* fax;
 };
 
 #endif