2016-07-19 21 views
0

Gelegentlich stoßen wir auf extrem große PDF-Dateien, die mit vollständigen, hochauflösenden Bildern gefüllt sind (das Ergebnis des Scannens von Dokumenten). Zum Beispiel habe ich eine 1,7 GB PDF mit 3500 Bildern. Das Laden des Dokuments dauert etwa 50 Sekunden, aber das Zählen der Bilder dauert etwa 15 Minuten.Schnellste Methode zum Zählen von PDF-Bildern mit der PDFBox 2.x

Ich bin sicher, das ist, weil die Bild-Bytes als Teil der API-Aufrufe gelesen werden. Gibt es eine Möglichkeit, die Bildanzahl zu extrahieren, ohne die Bildbytes tatsächlich zu lesen?

PDFBox Version: 2.0.2

Beispielcode:

@Test 
public void imageCountIsCorrect() throws Exception { 
    PDDocument pdf = readPdf(); 
    try { 
     assertEquals(3558, countImages(pdf)); 
     // assertEquals(3558, countImagesWithExtractor(pdf)); 
    } finally { 
     if (pdf != null) { 
      pdf.close(); 
     } 
    } 
} 

protected PDDocument readPdf() throws IOException { 
    StopWatch stopWatch = new StopWatch(); 
    stopWatch.start(); 

    FileInputStream stream = new FileInputStream("large.pdf"); 
    PDDocument pdf; 
    try { 
     pdf = PDDocument.load(stream, MemoryUsageSetting.setupMixed(1024 * 1024 * 250)); 
    } finally { 
     stream.close(); 
    } 

    stopWatch.stop(); 
    log.info("PDF loaded: time={}s", stopWatch.getTime()/1000); 
    return pdf; 
} 


protected int countImages(PDDocument pdf) throws IOException { 
    StopWatch stopWatch = new StopWatch(); 
    stopWatch.start(); 

    int imageCount = 0; 
    for (PDPage pdPage : pdf.getPages()) { 
     PDResources pdResources = pdPage.getResources(); 
     for (COSName cosName : pdResources.getXObjectNames()) { 
      PDXObject xobject = pdResources.getXObject(cosName); 
      if (xobject instanceof PDImageXObject) { 
       imageCount++; 
       if (imageCount % 100 == 0) { 
        log.info("Found image: #" + imageCount); 
       } 
      } 
     } 
    } 

    stopWatch.stop(); 
    log.info("Images counted: time={}s,imageCount={}", stopWatch.getTime()/1000, imageCount); 
    return imageCount; 
} 

Wenn ich die countImages Methode zu verlassen sich auf die COSName ändern, vervollständigt die Zählung in weniger als 1 s, aber ich bin ein wenig unsicher sich auf das Präfix des Namens verlassen. Dies scheint ein Nebenprodukt des pdf-Encoder zu sein und nicht PDFBox (ich jeden Hinweis nicht, um es in ihrem Code finden konnte):

if (cosName.getName().startsWith("QuickPDFIm")) { 
    imageCount++; 
} 
+1

Als Randbemerkung, Ihr Code zählt nur die unmittelbare Bitmap-Bild * Ressourcen * pro Seite . Es besteht weder aus eingebetteten Bildern noch aus Bildern, die in Objekten oder Mustern enthalten sind. Auf der anderen Seite muss eine Bildressource nicht auf einer Seite verwendet werden. Daher werden manchmal auch zu viele Bilder gezählt. Bei einer generischen Lösung müssen Sie den Content-Stream berücksichtigen. – mkl

+0

Ahh, das würde einige der Inkonsistenzen erklären, die ich zwischen Bildzählungen gefunden habe, als ich eine benutzerdefinierte Implementierung von PDFGraphicsStreamEngine zum Zählen von Bildern verwendet habe. Ich werde diesen Code untersuchen, um herauszufinden, was ich vermisse. Vielen Dank! –

+1

Was ich im Sinn habe ist, das ExtractImages-Beispiel zu modifizieren und alles zu entfernen, was Image-Objekte erzeugt, und 'addOperator (new DrawObject());' mit einem 'DrawObject extends GraphicsOperatorProcessor'-Prozessor aufzurufen, der die xobjects nicht erzeugen würde, wenn es ein ist Bild aber würde Formen folgen. Siehe den Quellcode von org.apache.pdfbox.contentstream.operator.DrawObject. –

Antwort

0

So der bisherige Ansatz einige zusätzliche Fehler hatte (Inline-Bilder verpassen könnte, etc .). Danke mkl und Tilman Hausherr für das Feedback!

TIL - PDF object streams contain useful operator codes!

Mein neuer Ansatz erweitert PDFStreamEngine und erhöht eine imageCount für jeden 'Do' (Objekt zeichnet) Operator in dem PDF-Inhaltsstrom gefunden. Die Bildanzahl dauert nur wenige hundert Millisekunden mit dieser Methode:

public class PdfImageCounter extends PDFStreamEngine { 
    protected int documentImageCount = 0; 

    public int getDocumentImageCount() { 
     return documentImageCount; 
    } 

    public PdfImageCounter() { 
     addOperator(new OperatorProcessor() { 
      @Override 
      public void process(Operator operator, List<COSBase> arguments) throws IOException { 
       if (arguments.size() < 1) { 
        throw new MissingOperandException(operator, arguments); 
       } 
       if (isImage(arguments.get(0))) { 
        documentImageCount++; 
       } 
      } 

      protected Boolean isImage(COSBase base) { 
       return (base instanceof COSName) && 
         context.getResources().isImageXObject((COSName)base); 
      } 

      @Override 
      public String getName() { 
       return "Do"; 
      } 
     }); 
    } 
} 

es für jede Seite aufrufen:

protected int countImagesWithProcessor(PDDocument pdf) throws IOException { 
    StopWatch stopWatch = new StopWatch(); 
    stopWatch.start(); 

    PdfImageCounter counter = new PdfImageCounter(); 
    for (PDPage pdPage : pdf.getPages()) { 
     counter.processPage(pdPage); 
    } 

    stopWatch.stop(); 
    int imageCount = counter.getDocumentImageCount(); 
    log.info("Images counted: time={}s,imageCount={}", stopWatch.getTime()/1000, imageCount); 
    return imageCount; 
} 
+1

Aber Sie kriechen keine Operanden, die keine Bilder sind, z.B. PDFormXObject. Sehen Sie sich org.apache.pdfbox.contentstream.operator.DrawObject an. Dieser hat eine interessante Strategie, um Bilder zu vermeiden. –

+0

Danke! Ich habe die Antwort aktualisiert, um Objekte zu überspringen, die nicht wirklich Bilder sind. –

+0

Was ich meinte, ist mehr als das. Wenn Sie ein Formular oder eine Transparenzgruppe treffen, müssen Sie auch das bearbeiten, wie zum Beispiel org.apache.pdfbox.contentstream.operator.DrawObject. Diese können auch Bilder enthalten. Als nächstes müssen Sie sicherstellen, dass die Bilder eindeutig sind, verwenden Sie ein Set . –