Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Locale;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

Expand All @@ -24,7 +25,12 @@ public class ExtractImagesService {
* Extract all images from a PDF and return them as a ZIP file.
*/
public byte[] extractImages(byte[] pdfBytes, String format) throws IOException {
String imgFormat = (format != null && !format.isBlank()) ? format : "png";
String imgFormat = (format != null && !format.isBlank())
? format.toLowerCase(Locale.ROOT)
: "png";
if (!imgFormat.equals("png") && !imgFormat.equals("jpg") && !imgFormat.equals("jpeg")) {
throw new IllegalArgumentException("Unsupported image output format: " + imgFormat);
}

try (PDDocument doc = Loader.loadPDF(new RandomAccessReadBuffer(pdfBytes));
ByteArrayOutputStream zipOut = new ByteArrayOutputStream();
Expand All @@ -35,18 +41,20 @@ public byte[] extractImages(byte[] pdfBytes, String format) throws IOException {
for (int pageIdx = 0; pageIdx < doc.getNumberOfPages(); pageIdx++) {
PDPage page = doc.getPage(pageIdx);
PDResources resources = page.getResources();
if (resources == null) continue;
if (resources == null) {
continue;
}

for (COSName name : resources.getXObjectNames()) {
PDXObject xobj = resources.getXObject(name);
if (xobj instanceof PDImageXObject image) {
BufferedImage bimg = image.getImage();
ByteArrayOutputStream imgBytes = new ByteArrayOutputStream();
ImageIO.write(bimg, imgFormat, imgBytes);

String entryName = String.format("page%d_img%d.%s", pageIdx + 1, imgIndex++, imgFormat);
zos.putNextEntry(new ZipEntry(entryName));
zos.write(imgBytes.toByteArray());
if (!ImageIO.write(bimg, imgFormat, zos)) {
throw new IllegalArgumentException("Unsupported image output format: " + imgFormat);
}
zos.closeEntry();
Comment on lines 53 to 58

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Covered in ExtractImagesServiceTest: extractImages_returnsZipWithDecodableImages opens the ZIP, checks the page1_img1.png entry name, and verifies the entry decodes through ImageIO.read(...); extractImages_throwsForUnsupportedFormat locks the invalid-format behavior to IllegalArgumentException. Focused Gradle run passed 3/3.

}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,11 @@ public byte[] convert(byte[] pdfBytes, String format, int dpi) throws IOExceptio
for (int i = 0; i < doc.getNumberOfPages(); i++) {
BufferedImage image = renderer.renderImageWithDPI(i, dpi, ImageType.RGB);

ByteArrayOutputStream imgOut = new ByteArrayOutputStream();
ImageIO.write(image, imageioFormat, imgOut);

ZipEntry entry = new ZipEntry(String.format("page_%03d.%s", i + 1, fmt));
zos.putNextEntry(entry);
zos.write(imgOut.toByteArray());
if (!ImageIO.write(image, imageioFormat, zos)) {
throw new IOException("Unsupported image output format: " + imageioFormat);
}
zos.closeEntry();
Comment on lines 34 to 39

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Covered in PdfToImageServiceTest.convert_returnsZipWithDecodablePageImages: the test opens the returned bytes with ZipInputStream, checks the expected page_001.png / page_002.png entry names, reads each ZIP entry, and verifies ImageIO.read(...) returns a decodable image. Focused Gradle run passed 3/3.

}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,14 @@

import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageContentStream;
import org.apache.pdfbox.pdmodel.common.PDRectangle;
import org.apache.pdfbox.pdmodel.graphics.image.LosslessFactory;
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;

import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;

Expand Down Expand Up @@ -34,4 +40,34 @@ public static byte[] createPdf(int pageCount) throws IOException {
public static byte[] createPdf() throws IOException {
return createPdf(1);
}

/**
* Returns a single-page PDF with one embedded image XObject.
*/
public static byte[] createPdfWithImage() throws IOException {
try (PDDocument doc = new PDDocument();
ByteArrayOutputStream out = new ByteArrayOutputStream()) {
PDPage page = new PDPage(PDRectangle.A4);
doc.addPage(page);

BufferedImage image = new BufferedImage(32, 32, BufferedImage.TYPE_INT_RGB);
Graphics2D graphics = image.createGraphics();
try {
graphics.setColor(Color.CYAN);
graphics.fillRect(0, 0, 32, 32);
graphics.setColor(Color.BLUE);
graphics.fillOval(8, 8, 16, 16);
} finally {
graphics.dispose();
}

PDImageXObject ximage = LosslessFactory.createFromImage(doc, image);
try (PDPageContentStream content = new PDPageContentStream(doc, page)) {
content.drawImage(ximage, 72, 720, 32, 32);
}

doc.save(out);
return out.toByteArray();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package com.crystalpdf.backend.service;

import com.crystalpdf.backend.helper.PdfTestHelper;
import org.junit.jupiter.api.Test;

import javax.imageio.ImageIO;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;

class ExtractImagesServiceTest {

private final ExtractImagesService extractImagesService = new ExtractImagesService();

@Test
void extractImages_returnsZipWithDecodableImages() throws Exception {
byte[] pdf = PdfTestHelper.createPdfWithImage();
byte[] zipBytes = extractImagesService.extractImages(pdf, "png");

try (ZipInputStream zip = new ZipInputStream(new ByteArrayInputStream(zipBytes))) {
ZipEntry entry = zip.getNextEntry();
assertThat(entry).isNotNull();
assertThat(entry.getName()).isEqualTo("page1_img1.png");
assertThat(ImageIO.read(new ByteArrayInputStream(readEntry(zip)))).isNotNull();
zip.closeEntry();

assertThat(zip.getNextEntry()).isNull();
}
}

@Test
void extractImages_throwsForUnsupportedFormat() throws Exception {
byte[] pdf = PdfTestHelper.createPdfWithImage();

assertThatThrownBy(() -> extractImagesService.extractImages(pdf, "tiff"))
.isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("Unsupported image output format: tiff");
}

private static byte[] readEntry(ZipInputStream zip) throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream();
zip.transferTo(out);
return out.toByteArray();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package com.crystalpdf.backend.service;

import com.crystalpdf.backend.helper.PdfTestHelper;
import org.junit.jupiter.api.Test;

import javax.imageio.ImageIO;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

import static org.assertj.core.api.Assertions.assertThat;

class PdfToImageServiceTest {

private final PdfToImageService pdfToImageService = new PdfToImageService();

@Test
void convert_returnsZipWithDecodablePageImages() throws Exception {
byte[] pdf = PdfTestHelper.createPdf(2);
byte[] zipBytes = pdfToImageService.convert(pdf, "png", 72);

try (ZipInputStream zip = new ZipInputStream(new ByteArrayInputStream(zipBytes))) {
ZipEntry first = zip.getNextEntry();
assertThat(first).isNotNull();
assertThat(first.getName()).isEqualTo("page_001.png");
assertThat(ImageIO.read(new ByteArrayInputStream(readEntry(zip)))).isNotNull();
zip.closeEntry();

ZipEntry second = zip.getNextEntry();
assertThat(second).isNotNull();
assertThat(second.getName()).isEqualTo("page_002.png");
assertThat(ImageIO.read(new ByteArrayInputStream(readEntry(zip)))).isNotNull();
zip.closeEntry();

assertThat(zip.getNextEntry()).isNull();
}
}

private static byte[] readEntry(ZipInputStream zip) throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream();
zip.transferTo(out);
return out.toByteArray();
}
}