From 93f1aa83d31d48e705759c007d0f3404a5b2bb86 Mon Sep 17 00:00:00 2001 From: Georgy Litvinov Date: Mon, 7 Dec 2020 19:21:00 +0100 Subject: [PATCH] Crop and convert to JPG embeeded images --- .../w2phtml/base/BinaryGraphicsDocument.java | 114 +++++++++++++++++- src/main/java/w2phtml/office/MIMETypes.java | 12 ++ src/main/java/w2phtml/office/XMLString.java | 1 + .../w2phtml/xhtml/content/DrawParser.java | 2 + 4 files changed, 123 insertions(+), 6 deletions(-) diff --git a/src/main/java/w2phtml/base/BinaryGraphicsDocument.java b/src/main/java/w2phtml/base/BinaryGraphicsDocument.java index 70e96f3..253e453 100644 --- a/src/main/java/w2phtml/base/BinaryGraphicsDocument.java +++ b/src/main/java/w2phtml/base/BinaryGraphicsDocument.java @@ -25,12 +25,25 @@ package w2phtml.base; +import static w2phtml.office.XMLString.FO_CLIP; + +import java.awt.image.BufferedImage; +import java.awt.image.RasterFormatException; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; import java.io.OutputStream; +import javax.imageio.ImageIO; + +import org.apache.commons.imaging.ImageInfo; +import org.apache.commons.imaging.ImageReadException; +import org.apache.commons.imaging.Imaging; + import w2phtml.api.OutputFile; - -import java.io.IOException; - +import w2phtml.office.MIMETypes; +import w2phtml.office.StyleWithProperties; +import w2phtml.util.DimensionsConverter; /** This class is used to represent a binary graphics document to be included in the converter result. * I may also represent a linked image, which should not be included (and will produce an empty file @@ -49,6 +62,9 @@ public class BinaryGraphicsDocument implements OutputFile { private byte[] blob = null; private int nOff = 0; private int nLen = 0; + private boolean cropped = false; + private float horizontalPPI = 96.0F; + private float verticalPPI = 96.0F; /**Constructs a new graphics document. * Until data is added using the read methods, the document is considered a link to @@ -75,9 +91,27 @@ public class BinaryGraphicsDocument implements OutputFile { this.blob = bgd.getData(); this.bAcceptedFormat = bgd.isAcceptedFormat(); this.bRecycled = true; + } - /** Is this graphics document recycled? + private void extractPPI() { + try { + ImageInfo info = Imaging.getImageInfo(blob); + int heightDPI = info.getPhysicalHeightDpi(); + if (heightDPI > 0) { + this.verticalPPI = heightDPI; + } + int widthDPI = info.getPhysicalWidthDpi(); + if (widthDPI > 0) { + this.horizontalPPI = widthDPI; + } + } catch (ImageReadException | IOException e) { + e.printStackTrace(); + } + + } + + /** Is this graphics document recycled? * * @return true if this is the case */ @@ -170,8 +204,76 @@ public class BinaryGraphicsDocument implements OutputFile { public boolean isMasterDocument() { return false; } - - /** Does this document contain formulas? + + public boolean isCropped() { + return cropped; + } + + /* Crop image from byte array */ + public void cropImage(StyleWithProperties style) { + if (blob == null || style == null) { + return; + } + if (sMimeType == null || ( + !sMimeType.equals(MIMETypes.PNG) + && !sMimeType.equals(MIMETypes.JPEG) + && !sMimeType.equals(MIMETypes.GIF) + && !sMimeType.equals(MIMETypes.TIFF) + )){ + return; + } + extractPPI(); + int[] offsets = getOffsets(style); + ByteArrayInputStream bis = new ByteArrayInputStream(blob); + + try { + BufferedImage image = ImageIO.read(bis); + int height = image.getHeight(); + int width = image.getWidth(); + String formatName = MIMETypes.getFormatType(blob); + int xTopLeft = offsets[3]; + int yTopLeft = offsets[0]; + int xBottomRight = width - offsets[3] - offsets[1]; + int yBottomRight = height - offsets[0] - offsets[2]; + BufferedImage croppedImage = image.getSubimage(xTopLeft, yTopLeft, xBottomRight , yBottomRight ); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ImageIO.write(croppedImage, "JPG", baos); + sMimeType = MIMETypes.JPEG; + blob = baos.toByteArray(); + cropped = true; + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (RasterFormatException e ) { + e.printStackTrace(); + } + + } + + private int[] getOffsets(StyleWithProperties style) { + /* top, right, bottom, left */ + int[] offsets = { 0, 0, 0, 0 }; + String fo_clip = style.getProperty(FO_CLIP); + if (!fo_clip.startsWith("rect(") || !fo_clip.endsWith(")")) { + return offsets; + } + fo_clip = fo_clip.replaceAll("^rect\\(", ""); + fo_clip = fo_clip.replaceAll("\\)", ""); + String[] inputOffsets = fo_clip.split("\\s*,\\s*"); + for (int i = 0; i < inputOffsets.length; i++) { + String inputOffset = inputOffsets[i]; + float ppi; + if (i % 2 == 0) { + ppi = verticalPPI; + } else { + ppi = horizontalPPI; + } + offsets[i] = Math.round(DimensionsConverter.length2px(inputOffset, ppi)); + } + return offsets; + } + + /** Does this document contain formulas? * * @return false - a graphics file does not contain formulas */ diff --git a/src/main/java/w2phtml/office/MIMETypes.java b/src/main/java/w2phtml/office/MIMETypes.java index d75d42b..72ed3c5 100644 --- a/src/main/java/w2phtml/office/MIMETypes.java +++ b/src/main/java/w2phtml/office/MIMETypes.java @@ -124,6 +124,18 @@ public final class MIMETypes extends w2phtml.api.MIMETypes { if (isSVG(blob)) { return SVG; } return ""; } + + public static final String getFormatType(byte[] blob) { + if (isType(blob,PNG_SIG)) { return "PNG"; } + if (isType(blob,JPEG_SIG)) { return "JPG"; } + if (isType(blob,JPEG_EXIF_SIG)) { return "JPG"; } + if (isType(blob,GIF87_SIG)) { return "GIF"; } + if (isType(blob,GIF89_SIG)) { return "GIF"; } + if (isType(blob,TIFF_SIG)) { return "TIFF"; } + if (isType(blob,BMP_SIG)) { return "BMP"; } + if (isSVG(blob)) { return "SVG"; } + return ""; + } public static final String getFileExtension(String sMIME) { if (PNG.equals(sMIME)) { return PNG_EXT; } diff --git a/src/main/java/w2phtml/office/XMLString.java b/src/main/java/w2phtml/office/XMLString.java index 6c37936..f5bd565 100644 --- a/src/main/java/w2phtml/office/XMLString.java +++ b/src/main/java/w2phtml/office/XMLString.java @@ -434,6 +434,7 @@ public class XMLString { // fo namespace public static final String FO_LANGUAGE = "fo:language"; public static final String FO_COUNTRY = "fo:country"; + public static final String FO_CLIP = "fo:clip"; public static final String FO_TEXT_SHADOW = "fo:text-shadow"; public static final String FO_COLOR = "fo:color"; public static final String FO_BACKGROUND_COLOR = "fo:background-color"; diff --git a/src/main/java/w2phtml/xhtml/content/DrawParser.java b/src/main/java/w2phtml/xhtml/content/DrawParser.java index 2a1700e..29ff9e9 100644 --- a/src/main/java/w2phtml/xhtml/content/DrawParser.java +++ b/src/main/java/w2phtml/xhtml/content/DrawParser.java @@ -539,6 +539,8 @@ public class DrawParser extends Parser { String sFileName = null; bgd = converter.getImageCv().getImage(onode); if (bgd!=null) { + StyleWithProperties style = ofr.getFrameStyle(frame.getAttribute(XMLString.DRAW_STYLE_NAME)); + bgd.cropImage(style); if (!bgd.isLinked()) { // embedded image sFileName = bgd.getFileName(); // If this is the cover image, add it to the converter result