Loading page as Java image

Help
2007-08-07
2012-11-08
  • Marco Schmidt
    Marco Schmidt
    2007-08-07

    This question has been asked before in similar form in one of the forums but wasn't answered.

    I'm trying to load the first page of a DJVU file as a BufferedImage at a reduced size (to be used in a thumbnail view) in a Java program. I don't want to run the applet or application. How do I do this? After studying the code and searching the documentation for two hours I haven't made much progress. I'm looking for something like this:

    BufferedImage img = DjVuLoader.load("sample.djvu", 160);

    where 160 is the larger side - width or height - of the image to be created. Thus, a DjVu file with a resolution of 3000 x 1500 pixels would return a thumbnail of 160 x 80 pixels which I can then process. How can I implement this?

    Regards,
    Marco Schmidt

     
    • Jesper Zedlitz
      Jesper Zedlitz
      2008-10-16

      public static boolean generateThumbnail(final String url, final int pageNumber, final int width, final int height, final String format, final File thumbnail)
      throws MalformedURLException, IOException {

      /\* disable debug output to System.out \*/
      com.lizardtech.djvu.DjVuOptions.out = com.lizardtech.djvu.DjVuOptions.err =
              new PrintStream\(new OutputStream\(\) \{public void write\(final int b\)\{\}\}\);
      
      final Document document = new Document\(\);
      document.read\(new URL\(url\)\);
      
      final DjVuPage\[\] page = \{ document.getPage\(0, DjVuPage.MAX\_PRIORITY, true\) \};
      final DjVuImage image = new DjVuImage\(page, false\);
      
      Rectangle bounds = image.computeScaledBounds\(DjVuImage.FIT\_PAGE, new  Dimension\(width,height\)\);
      
      Image\[\] awtImages = image.getScaledInstance\(bounds.width, bounds.height\).getImage\(new Frame\(\), bounds\);
      
      if \(\(awtImages \!= null\) && \(awtImages.length > 0\) && \(awtImages\[0\] \!= null\)\) \{
          Image awtImage = awtImages\[0\];
          BufferedImage bimg =new BufferedImage\(bounds.width, bounds.height, BufferedImage.TYPE\_INT\_RGB\);
         Graphics graphics = bimg.getGraphics\(\);
         graphics.drawImage\(awtImage, 0, 0, null\);
         ImageIO.write\(bimg, format, thumbnail\);
         return true;
      \}
      

      return false;
      }

       
    • You probably just want to do something like:

      DjVuImage djvuImage = new DjVuImage("sample.djvu",false);
      Dimension size = new Dimension(160,160);
      Rectangle bounds = djvuImage.computeScaledBounds(DjVuImage.FIT_PAGE,size);
      Image image = djvuImage.getImage(parent,bounds);

      Bill

       
      • Marco Schmidt
        Marco Schmidt
        2007-08-10

        Thanks for the quick reply. The getImage in the DjVuImage class I downloaded only returns Image[], so I take the first element, which seems to be right. I'm also using a DjVuBean object to find out the number of pages and DPI of the document, something I want to display to users with the thumbnail.

        1)
        Please tell me if I'm doing anything wrong (my code, inserted below, seems to work with the djvu files I have, but that's not much of a test).

        2)
        Also, is there a way to turn off all those status messages to System.out?

        3)
        Are there other interesting metadata items available in a Djvu file that I overlooked? Like an "author", "date of creation" or "title" field?

        Regards,
        Marco Schmidt

        public boolean extract(File file, Metadata metadata)
        {
        try
        {
        DjVuBean bean = new DjVuBean();
        URL url = file.toURL();
        bean.setURL(url);
        int numPages = bean.getDocumentSize();
        if (numPages <= 0)
        {
        return false;
        }
        metadata.setFileFormat(FileFormat.DJVU);
        metadata.setFileType(FileType.DOCUMENT);
        Adapter.setDocumentNumPages(metadata, numPages);
        bean.setPage(1);
        int dpi = bean.getDPI();
        if (dpi > 0)
        {
        Adapter.setPhysicalResolutionDpi(metadata, dpi);
        }
        DjVuImage image = bean.getImage();
        Dimension size = new Dimension(160, 160);
        Rectangle bounds = image.computeScaledBounds(DjVuImage.FIT_PAGE, size);
        Image[] img = image.getImage(new Canvas(), bounds);
        if (img != null && img.length > 0 && img[0] != null)
        {
        BufferedImage bimg = new BufferedImage((int)bounds.getWidth(),
        (int)bounds.getHeight(), BufferedImage.TYPE_INT_RGB);
        Graphics graphics = bimg.getGraphics();
        graphics.drawImage(img[0], 0, 0, null);
        metadata.setThumbnail(ThumbnailCreator.encodeAsJpeg(bimg));
        }
        return true;
        }
        catch (Exception e)
        {
        e.printStackTrace();
        }
        return false;
        }

         
        • You are right, getImage() does define an array. Looking at the code, I see it always contains exactly one element.

          To suppress all output try:

          com.lizardtech.djvu.DjVuOptions.out =
          com.lizardtech.djvu.DjVuOptions.err =
          new PrintStream(new OutputSteam(){public void write(int c){}});

          or better, send the output to a log file.

          Bill

           
          • Marco Schmidt
            Marco Schmidt
            2007-08-11

            I'm a bit puzzled. Changing nothing substantial in the code, my thumbnails now are no longer small versions of the complete image of the first page but what seems to be the upper left corner of the first page at maximum resolution. I tried to find out what caused this, even went back to the code as I posted it here, to no avail. I know that this is not a sufficient error description, but I have no idea what changed. Same IDE, same version of the Djvu library, same sample files and -- at the end -- even the same code using the library. The line

                    Rectangle bounds = image.computeScaledBounds\(DjVuImage.FIT\_PAGE, size\);
            

            which (to me) seems to be responsible for choosing the image's looks remains unchanged.

            The one thing I could think of - is there maybe some issue with threads running in parallel, the graphics.drawImage line maybe drawing before the complete image is loaded? It worked before, but something must have changed.

            Then there's the issue of exceptions which are sometimes - not always - thrown. I cannot reliably reproduce those. They look like this (with the Scientific American sample file):

            java.io.FileNotFoundException: G:\test\Cover1_0001.djvu (Das System kann die angegebene Datei nicht finden)
            at java.io.FileInputStream.open(Native Method)
            at java.io.FileInputStream.<init>(Unknown Source)
            at java.io.FileInputStream.<init>(Unknown Source)
            at sun.net.www.protocol.file.FileURLConnection.connect(Unknown Source)
            at sun.net.www.protocol.file.FileURLConnection.getInputStream(Unknown Source)
            at com.lizardtech.djvu.DataPool.readBlock(DataPool.java:282)
            at com.lizardtech.djvu.DataPool.getBlock(DataPool.java:219)
            at com.lizardtech.djvu.CachedInputStream.read(CachedInputStream.java:290)
            at com.lizardtech.djvu.Document.insert_file(Document.java:691)
            at com.lizardtech.djvu.Document.insert_file(Document.java:646)
            at com.lizardtech.djvu.Document.insert_file(Document.java:620)
            at com.lizardtech.djvu.Document.insert_file(Document.java:598)
            at com.lizardtech.djvu.Document.get_data(Document.java:453)
            at com.lizardtech.djvu.Document.run(Document.java:997)
            at java.lang.Thread.run(Unknown Source)

            "Das System kann die angegebene Datei nicht finden" means "System cannot find file". Is Cover1_0001.djvu the internal name of the first page of the Djvu file? For some reason, the library is trying to load that page with this name directly from the file system (same directory as the DJVU file), which of course fails because the name of the file is "scientificamerican.djvu". This exception is followed by the same one, except that the file name now is "page001.djvu". Those two appear six times each, alternating, so Cover1_0001.djvu / page001.djvu / Cover1_0001.djvu / page001.djvu ...

            As a side issue, it seems that the DJVU files are not closed while the application is running (they cannot be moved under Windows). This is also not acceptable, but maybe this is solved by solving the other issues.

            I'd appreciate any help.

            Regards,
            Marco Schmidt

            Here is the complete version of the code. I moved away from file.toURL because it is deprecated, but this doesn't seem to be the problem, changing it back to file.toURL doesn't make a difference. I don't have to open the file with the bean, but the code you first inserted:

            DjVuImage djvuImage = new DjVuImage("sample.djvu",false);

            doesn't work because there is no such DjVuImage constructor.

                    DjVuBean bean = new DjVuBean\(\);
                    URI uri = file.toURI\(\);
                    URL url = uri.toURL\(\);
                    bean.setURL\(url\);
                    int numPages = bean.getDocumentSize\(\);
                    if \(numPages &lt;= 0\)
                    \{
                        return false;
                    \}
                    metadata.setFileFormat\(FileFormat.DJVU\);
                    metadata.setFileType\(FileType.DOCUMENT\);
                    Adapter.setDocumentNumPages\(metadata, numPages\);
            
                    bean.setPage\(1\);
                    int dpi = bean.getDPI\(\);
                    if \(dpi &gt; 0\)
                    \{
                        Adapter.setPhysicalResolutionDpi\(metadata, dpi\);
                    \}
                    Dimension maxSize = bean.getMaximumSize\(\);
                    int width = maxSize.width;
                    int height = maxSize.height;
                    if \(dpi &gt; 0 &amp;&amp; width &gt; 0 &amp;&amp; height &gt; 0\)
                    \{
                        final int MICROMETERS\_PER\_INCH = 25400; 
                        Adapter.setPhysicalSizeMicros\(metadata,
                            \(long\)\(\(double\)width / dpi \* MICROMETERS\_PER\_INCH\),
                            \(long\)\(\(double\)height / dpi \* MICROMETERS\_PER\_INCH\)\);
                    \}
            
                    DjVuImage image = bean.getImage\(\);
                    Dimension size = new Dimension\(160, 160\); 
                    Rectangle bounds = image.computeScaledBounds\(DjVuImage.FIT\_PAGE, size\); 
                    Image\[\] img = image.getImage\(new Canvas\(\), bounds\);
                    if \(img \!= null &amp;&amp; img.length &gt; 0 &amp;&amp; img\[0\] \!= null\)
                    \{
                        BufferedImage bimg = new BufferedImage\(\(int\)bounds.getWidth\(\),
                            \(int\)bounds.getHeight\(\), BufferedImage.TYPE\_INT\_RGB\);
                        Graphics graphics = bimg.getGraphics\(\);
                        graphics.drawImage\(img\[0\], 0, 0, null\);
                        metadata.setThumbnail\(ThumbnailCreator.encodeAsJpeg\(bimg\)\);
                    \}
            
                    return true;
            
             
            • Sorry, instead of:

              DjVuImage djvuImage = new DjVuImage("sample.djvu",false);

              I should have wrote:

              final Document document = new Document();
              document.setAsync(true);
              document.read(url);
              final DjVuPage [] page = { document.getPage(0, DjVuPage.MAX_PRIORITY, true) };
              final DjVuImage image = new DjVuImage(page,false);

              If you are not using the DjVuBean class to display, then I suggest avoid using it. DjVuBean does some funky things for panning and such, which could be causing problems.

              Are you using indirect files? If so, you need to make sure you have all the page files available. If you are dealing with bundled documents, then you should not be seeing exceptions loading pages. If you are seeing the exception reported on a bundled documents, then it indicates there is an error either in my java code or the syntax of the URL provided.

              Bill

               
              • Marco Schmidt
                Marco Schmidt
                2007-08-15

                I tried your code and removed all references to the bean class, but the error is the same: instead of a scaled-down version I get a thumbnail area from the upper left section. The bounds as computed in the code seem to be correct (108 x 160, in one case, for a portrait mode document). The files are scientificamerican from the example section of the website and some other DJVU file. No idea if they're "indirect". What is so annoying is the fact that everything worked fine and just stopped working for no apparent reason with no changes to anything in the code or the environment. Obviously, something must be different, but I have no clue what that might be.

                Below's my code. I give a new Frame() (as in java.awt.Frame) to image.getImage because it needs some AWT component. Is that maybe the source of the error?

                Thanks for your help so far.

                public boolean extract\(File file, Metadata metadata\)
                \{
                    try
                    \{
                        URI uri = file.toURI\(\);
                        URL url = uri.toURL\(\);
                        final Document document = new Document\(\); 
                        document.setAsync\(true\); 
                        document.read\(url\); 
                        final DjVuPage\[\] page = \{ document.getPage\(0, DjVuPage.MAX\_PRIORITY, true\) \}; 
                        final DjVuImage image = new DjVuImage\(page, false\); 
                        metadata.setFileFormat\(FileFormat.DJVU\);
                        metadata.setFileType\(FileType.DOCUMENT\);
                        Dimension size = new Dimension\(160, 160\); 
                        Rectangle bounds = image.computeScaledBounds\(DjVuImage.FIT\_PAGE, size\); 
                        Image\[\] awtImages = image.getImage\(new Frame\(\), bounds\);
                        if \(awtImages \!= null &amp;&amp; awtImages.length &gt; 0 &amp;&amp; awtImages\[0\] \!= null\)
                        \{
                            Image awtImage = awtImages\[0\];
                            BufferedImage bimg = new BufferedImage\(\(int\)bounds.getWidth\(\),
                                \(int\)bounds.getHeight\(\), BufferedImage.TYPE\_INT\_RGB\);
                            Graphics graphics = bimg.getGraphics\(\);
                            graphics.drawImage\(awtImage, 0, 0, null\);
                            metadata.setThumbnail\(ThumbnailCreator.encodeAsJpeg\(bimg\)\);
                        \}
                        return true;
                    \}
                    catch \(Exception e\)
                    \{
                        e.printStackTrace\(\);
                    \}
                    return false;
                \}
                
                 
    • BTW. I haven't tried converting the Image to a BufferedImage, but according to what I've seen on the web, the following should work:

      BufferedImage bimage = new BufferedImage(160,160,BufferedImage.TYPE_INT_RGB);
      Graphics bg=bimage.createGraphics();
      bg.drawImage(bimage,0,0,this);
      bg.dispose();

       
    • Jesper Zedlitz
      Jesper Zedlitz
      2008-10-16

      In case someond finds this discussion (like me) and looks for a solution:

      You have to request a scaled version of the image. Replace the line
      Image[] awtImages = image.getImage(new Frame(), bounds);
      with
      Image[] awtImages = image.getScaledInstance((int) bounds.getWidth(), (int) bounds.getHeight()).getImage(new Frame(), bounds);