public static void displayImageInfo(ImageInfo info) { ImageSize size = info.getSize(); Dimension2D dPt = size.getDimensionPt(); Dimension dPx = size.getDimensionPx(); log.debug( info.getOriginalURI() + " " + info.getMimeType() + " " + Math.round(dPx.getWidth()) + "x" + Math.round(dPx.getHeight())); log.debug( "Resolution:" + Math.round(size.getDpiHorizontal()) + "x" + Math.round(size.getDpiVertical())); log.debug( "Print size: " + Math.round(dPt.getWidth() / 72) + "\" x" + Math.round(dPt.getHeight() / 72) + "\""); }
public static CxCy scale(ImageInfo imageInfo, double xEmu, double yEmu) { ImageSize size = imageInfo.getSize(); double iwEmu = toEmu(size.getWidthPx(), size.getDpiHorizontal()); double ihEmu = toEmu(size.getHeightPx(), size.getDpiVertical()); return scaleToFit(iwEmu, ihEmu, xEmu, yEmu); }
/** * Create a linked image part, and attach it as a rel of the specified source part (eg a header * part). * * <p>The current behaviour is that the part is added to the package, but since the target mode of * the rel is external, the part is redundant. * * @param wordMLPackage * @param sourcePart * @param url * @return * @throws Exception */ public static BinaryPartAbstractImage createLinkedImagePart( OpcPackage opcPackage, Part sourcePart, URL url) throws Exception { log.debug("Incoming url for linked image: " + url.toString()); ImageInfo info = ensureFormatIsSupported(url, null, null, false); // final param doesn't matter in this case ContentTypeManager ctm = opcPackage.getContentTypeManager(); String proposedRelId = sourcePart.getRelationshipsPart().getNextId(); // In order to ensure unique part name, // idea is to use the relId, which ought to be unique String ext = info.getMimeType().substring(info.getMimeType().indexOf("/") + 1); BinaryPartAbstractImage imagePart = (BinaryPartAbstractImage) ctm.newPartForContentType( info.getMimeType(), createImageName(opcPackage, sourcePart, proposedRelId, ext), null); // NB: contents never populated log.debug( "created part " + imagePart.getClass().getName() + " with name " + imagePart.getPartName().toString()); imagePart.rels.add( sourcePart.addTargetPart( imagePart)); // want to create rel with suitable name; side effect is to add part imagePart.getRelLast().setTargetMode("External"); opcPackage.getExternalResources().put(imagePart.getExternalTarget(), imagePart); // if (!url.getProtocol().equals("file") && new File(url.toString() ).isFile()) { // imagePart.rel.setTarget("file:///" + url); // } else { // imagePart.rel.setTarget(url.toString()); // } imagePart.getRelLast().setTarget(url.toString()); imagePart.setImageInfo(info); return imagePart; }
/** * Create an image part from the provided filePath image, attach it to the source part (eg the * main document part, a header part etc), and return it. * * <p>Works for both docx and pptx. * * @param opcPackage * @param sourcePart * @param filePath * @return * @throws Exception */ public static BinaryPartAbstractImage createImagePart( OpcPackage opcPackage, Part sourcePart, File imageFile) throws Exception { final byte[] locByte = new byte[1]; // We are in the case that image is not load (no byte Array) so isLoad is false ImageInfo info = ensureFormatIsSupported(imageFile, locByte, false); ContentTypeManager ctm = opcPackage.getContentTypeManager(); // Ensure the relationships part exists if (sourcePart.getRelationshipsPart() == null) { RelationshipsPart.createRelationshipsPartForPart(sourcePart); } String proposedRelId = sourcePart.getRelationshipsPart().getNextId(); String ext = info.getMimeType().substring(info.getMimeType().indexOf("/") + 1); BinaryPartAbstractImage imagePart = (BinaryPartAbstractImage) ctm.newPartForContentType( info.getMimeType(), createImageName(opcPackage, sourcePart, proposedRelId, ext), null); log.debug( "created part " + imagePart.getClass().getName() + " with name " + imagePart.getPartName().toString()); FileInputStream fis = new FileInputStream(imageFile); imagePart.setBinaryData(fis); imagePart.rels.add(sourcePart.addTargetPart(imagePart, proposedRelId)); imagePart.setImageInfo(info); return imagePart; }
private ICC_Profile buildICCProfile( final ImageInfo info, final ColorSpace colorSpace, final ByteArrayOutputStream iccStream) throws IOException { if (iccStream != null && iccStream.size() > 0) { if (log.isDebugEnabled()) { log.debug("Effective ICC profile size: " + iccStream.size()); } final int alignment = 4; final int padding = (alignment - iccStream.size() % alignment) % alignment; if (padding != 0) { try { iccStream.write(new byte[padding]); } catch (final IOException ioe) { throw new IOException("Error while aligning ICC stream: " + ioe.getMessage()); } } ICC_Profile iccProfile = null; try { iccProfile = ColorProfileUtil.getICC_Profile(iccStream.toByteArray()); if (log.isDebugEnabled()) { log.debug("JPEG has an ICC profile: " + iccProfile.toString()); } } catch (final IllegalArgumentException iae) { log.warn( "An ICC profile is present in the JPEG file but it is invalid (" + iae.getMessage() + "). The color profile will be ignored. (" + info.getOriginalURI() + ")"); return null; } if (iccProfile.getNumComponents() != colorSpace.getNumComponents()) { log.warn( "The number of components of the ICC profile (" + iccProfile.getNumComponents() + ") doesn't match the image (" + colorSpace.getNumComponents() + "). Ignoring the ICC color profile."); return null; } else { return iccProfile; } } else { return null; // no ICC profile available } }
/** * Create a <wp:inline> element suitable for this image, which can be _embedded_ in * w:p/w:r/w:drawing. * * @param filenameHint Any text, for example the original filename * @param altText Like HTML's alt text * @param id1 An id unique in the document * @param id2 Another id unique in the document * @param cx Image width in twip * @param link true if this is to be linked not embedded None of these things seem to be exposed * in Word 2007's user interface, but Word won't open the document if any of the attributes * these go in (except @ desc) aren't present! * @throws Exception */ public Inline createImageInline( String filenameHint, String altText, int id1, int id2, long cx, boolean link) throws Exception { ImageSize size = imageInfo.getSize(); Dimension2D dPt = size.getDimensionPt(); double imageWidthTwips = dPt.getWidth() * 20; log.debug("imageWidthTwips: " + imageWidthTwips); long cy; log.debug("Scaling image height to retain aspect ratio"); cy = UnitsOfMeasurement.twipToEMU(dPt.getHeight() * 20 * cx / imageWidthTwips); // Now convert cx to EMU cx = UnitsOfMeasurement.twipToEMU(cx); log.debug("cx=" + cx + "; cy=" + cy); return createImageInline(filenameHint, altText, id1, id2, cx, cy, link); }
public static CxCy scale(ImageInfo imageInfo, PageDimensions page) { double writableWidthTwips = page.getWritableWidthTwips(); log.debug("writableWidthTwips: " + writableWidthTwips); ImageSize size = imageInfo.getSize(); Dimension2D dPt = size.getDimensionPt(); double imageWidthTwips = dPt.getWidth() * 20; log.debug("imageWidthTwips: " + imageWidthTwips); long cx; long cy; boolean scaled = false; if (imageWidthTwips > writableWidthTwips) { log.debug("Scaling image to fit page width"); scaled = true; cx = UnitsOfMeasurement.twipToEMU(writableWidthTwips); cy = UnitsOfMeasurement.twipToEMU( dPt.getHeight() * 20 * writableWidthTwips / imageWidthTwips); } else { log.debug("Scaling image - not necessary"); cx = UnitsOfMeasurement.twipToEMU(imageWidthTwips); cy = UnitsOfMeasurement.twipToEMU(dPt.getHeight() * 20); } log.debug("cx=" + cx + "; cy=" + cy); return new CxCy(cx, cy, scaled); }
/** {@inheritDoc} */ public void activateLayout() { initialize(); FOUserAgent userAgent = pageSeq.getUserAgent(); ImageManager imageManager = userAgent.getFactory().getImageManager(); String uri = getExternalDocument().getSrc(); Integer firstPageIndex = ImageUtil.getPageIndexFromURI(uri); boolean hasPageIndex = (firstPageIndex != null); try { ImageInfo info = imageManager.getImageInfo(uri, userAgent.getImageSessionContext()); Object moreImages = info.getCustomObjects().get(ImageInfo.HAS_MORE_IMAGES); boolean hasMoreImages = moreImages != null && !Boolean.FALSE.equals(moreImages); Dimension intrinsicSize = info.getSize().getDimensionMpt(); ImageLayout layout = new ImageLayout(getExternalDocument(), this, intrinsicSize); PageSequence pageSequence = new PageSequence(null); transferExtensions(pageSequence); areaTreeHandler.getAreaTreeModel().startPageSequence(pageSequence); if (log.isDebugEnabled()) { log.debug("Starting layout"); } makePageForImage(info, layout); if (!hasPageIndex && hasMoreImages) { if (log.isTraceEnabled()) { log.trace("Starting multi-page processing..."); } URI originalURI; try { originalURI = new URI(URISpecification.escapeURI(uri)); int pageIndex = 1; while (hasMoreImages) { URI tempURI = new URI( originalURI.getScheme(), originalURI.getSchemeSpecificPart(), "page=" + Integer.toString(pageIndex + 1)); if (log.isTraceEnabled()) { log.trace("Subimage: " + tempURI.toASCIIString()); } ImageInfo subinfo = imageManager.getImageInfo( tempURI.toASCIIString(), userAgent.getImageSessionContext()); moreImages = subinfo.getCustomObjects().get(ImageInfo.HAS_MORE_IMAGES); hasMoreImages = moreImages != null && !Boolean.FALSE.equals(moreImages); intrinsicSize = subinfo.getSize().getDimensionMpt(); layout = new ImageLayout(getExternalDocument(), this, intrinsicSize); makePageForImage(subinfo, layout); pageIndex++; } } catch (URISyntaxException e) { getResourceEventProducer().uriError(this, uri, e, getExternalDocument().getLocator()); return; } } } catch (FileNotFoundException fnfe) { getResourceEventProducer().imageNotFound(this, uri, fnfe, getExternalDocument().getLocator()); } catch (IOException ioe) { getResourceEventProducer().imageIOError(this, uri, ioe, getExternalDocument().getLocator()); } catch (ImageException ie) { getResourceEventProducer().imageError(this, uri, ie, getExternalDocument().getLocator()); } }
private void makePageForImage(ImageInfo info, ImageLayout layout) { this.imageLayout = layout; curPage = makeNewPage(false, false); fillPage(info.getOriginalURI()); finishPage(); }
/** {@inheritDoc} */ @Override public Image loadImage(final ImageInfo info, final Map hints, final ImageSessionContext session) throws ImageException, IOException { if (!MimeConstants.MIME_JPEG.equals(info.getMimeType())) { throw new IllegalArgumentException( "ImageInfo must be from a image with MIME type: " + MimeConstants.MIME_JPEG); } ColorSpace colorSpace = null; boolean appeFound = false; int sofType = 0; ByteArrayOutputStream iccStream = null; final Source src = session.needSource(info.getOriginalURI()); final ImageInputStream in = ImageUtil.needImageInputStream(src); final JPEGFile jpeg = new JPEGFile(in); in.mark(); try { outer: while (true) { int reclen; final int segID = jpeg.readMarkerSegment(); if (log.isTraceEnabled()) { log.trace("Seg Marker: " + Integer.toHexString(segID)); } switch (segID) { case EOI: log.trace("EOI found. Stopping."); break outer; case SOS: log.trace("SOS found. Stopping early."); // TODO Not sure if // this is safe break outer; case SOI: case NULL: break; case SOF0: // baseline case SOF1: // extended sequential DCT case SOF2: // progressive (since PDF 1.3) case SOFA: // progressive (since PDF 1.3) sofType = segID; if (log.isTraceEnabled()) { log.trace("SOF: " + Integer.toHexString(sofType)); } in.mark(); try { reclen = jpeg.readSegmentLength(); in.skipBytes(1); // data precision in.skipBytes(2); // height in.skipBytes(2); // width final int numComponents = in.readUnsignedByte(); if (numComponents == 1) { colorSpace = ColorSpace.getInstance(ColorSpace.CS_GRAY); } else if (numComponents == 3) { colorSpace = ColorSpace.getInstance(ColorSpace.CS_LINEAR_RGB); } else if (numComponents == 4) { colorSpace = ColorSpaces.getDeviceCMYKColorSpace(); } else { throw new ImageException( "Unsupported ColorSpace for image " + info + ". The number of components supported are 1, 3 and 4."); } } finally { in.reset(); } in.skipBytes(reclen); break; case APP2: // ICC (see ICC1V42.pdf) in.mark(); try { reclen = jpeg.readSegmentLength(); // Check for ICC profile final byte[] iccString = new byte[11]; in.readFully(iccString); in.skipBytes(1); // string terminator (null byte) if ("ICC_PROFILE".equals(new String(iccString, "US-ASCII"))) { in.skipBytes(2); // chunk sequence number and total // number of chunks final int payloadSize = reclen - 2 - 12 - 2; if (ignoreColorProfile(hints)) { log.debug("Ignoring ICC profile data in JPEG"); in.skipBytes(payloadSize); } else { final byte[] buf = new byte[payloadSize]; in.readFully(buf); if (iccStream == null) { if (log.isDebugEnabled()) { log.debug("JPEG has an ICC profile"); final DataInputStream din = new DataInputStream(new ByteArrayInputStream(buf)); log.debug("Declared ICC profile size: " + din.readInt()); } // ICC profiles can be split into several // chunks // so collect in a byte array output stream iccStream = new ByteArrayOutputStream(); } iccStream.write(buf); } } } finally { in.reset(); } in.skipBytes(reclen); break; case APPE: // Adobe-specific (see 5116.DCT_Filter.pdf) in.mark(); try { reclen = jpeg.readSegmentLength(); // Check for Adobe header final byte[] adobeHeader = new byte[5]; in.readFully(adobeHeader); if ("Adobe".equals(new String(adobeHeader, "US-ASCII"))) { // The reason for reading the APPE marker is that // Adobe Photoshop // generates CMYK JPEGs with inverted values. The // correct thing // to do would be to interpret the values in the // marker, but for now // only assume that if APPE marker is present and // colorspace is CMYK, // the image is inverted. appeFound = true; } } finally { in.reset(); } in.skipBytes(reclen); break; default: jpeg.skipCurrentMarkerSegment(); } } } finally { in.reset(); } final ICC_Profile iccProfile = buildICCProfile(info, colorSpace, iccStream); if (iccProfile == null && colorSpace == null) { throw new ImageException("ColorSpace could not be identified for JPEG image " + info); } boolean invertImage = false; if (appeFound && colorSpace.getType() == ColorSpace.TYPE_CMYK) { if (log.isDebugEnabled()) { log.debug( "JPEG has an Adobe APPE marker. Note: CMYK Image will be inverted. (" + info.getOriginalURI() + ")"); } invertImage = true; } final ImageRawJPEG rawImage = new ImageRawJPEG( info, ImageUtil.needInputStream(src), sofType, colorSpace, iccProfile, invertImage); return rawImage; }
/** * @param bytes * @param imageFile * @return * @throws Exception * @throws FileNotFoundException * @throws IOException * @throws InterruptedException */ private static ImageInfo ensureFormatIsSupported( URL url, File imageFile, byte[] bytes, boolean isLoad) throws Docx4JException { FileOutputStream fos; // ImageInfo can also tell us what sort of image it is ImageInfo info = null; boolean imagePreloaderFound = true; try { try { info = getImageInfo(url); // Debug ... note that these figures // aren't necessarily accurate for EPS displayImageInfo(info); } catch (org.apache.xmlgraphics.image.loader.ImageException e) { // Assume: The file format is not supported. No ImagePreloader found for /tmp/img55623.img // There is no preloader for eg PDFs. // (To use an image natively, we do need a preloader) imagePreloaderFound = false; log.warn(e.getMessage()); } if (imagePreloaderFound && (info.getMimeType().equals(ContentTypes.IMAGE_TIFF) || info.getMimeType().equals(ContentTypes.IMAGE_EMF2) // ImageInfo || info.getMimeType().equals(ContentTypes.IMAGE_WMF) || info.getMimeType().equals(ContentTypes.IMAGE_PNG) || info.getMimeType().equals(ContentTypes.IMAGE_JPEG) || info.getMimeType().equals(ContentTypes.IMAGE_GIF) // || info.getMimeType().equals(ContentTypes.IMAGE_EPS) || info.getMimeType().equals(ContentTypes.IMAGE_BMP))) { // TODO: add other supported formats // If its a format Word supports natively, // do nothing here log.debug(".. supported natively by Word"); } else if (imageFile != null && bytes != null) { // otherwise (eg if its an EPS or PDF), try to convert it // Although the Word UI suggests you can embed an EPS // directly, Word actually converts it to an EMF; // Word is unable to read a plain EPS image part. // (TODO: detect failure) log.debug(".. attempting to convert to PNG"); // If image haven't been load (using function createImagePartFromFilePath), we load it if (isLoad == false) { // So first, we create tmpFile File tmpImageFile = File.createTempFile("img", ".img"); fos = new FileOutputStream(tmpImageFile); // Now we get the inputStream, which is represented by imageFile in this case FileInputStream bais = new FileInputStream(imageFile); // We convert convertToPNG(bais, fos, density); // We don't forget to change locFile because the new image file is the converted image // file!! imageFile = tmpImageFile; } // Else image has been load in an array (using function cretaImagePart) else { ByteArrayInputStream bais = new ByteArrayInputStream(bytes); fos = new FileOutputStream(imageFile); convertToPNG(bais, fos, density); } fos.close(); fos = null; // We need to refresh image info getImageManager().getCache().clearCache(); info = getImageInfo(new URL("file://" + imageFile.getAbsolutePath())); // Debug ... displayImageInfo(info); } else { throw new Docx4JException("Unsupported linked image type."); } } catch (Exception e) { throw new Docx4JException("Error checking image format", e); } return info; }
/** * Create an image part from the provided byte array, attach it to the source part (eg the main * document part, a header part etc), and return it. * * <p>Works for both docx and pptx. * * <p>Note: this method creates a temp file (and attempts to delete it). That's because it uses * org.apache.xmlgraphics * * @param opcPackage * @param sourcePart * @param bytes * @return * @throws Exception */ public static BinaryPartAbstractImage createImagePart( OpcPackage opcPackage, Part sourcePart, byte[] bytes) throws Exception { // Whatever image type this is, we're going to need // to know its dimensions. // For that we use ImageInfo, which can only // load an image from a URI. // So first, write the bytes to a temp file File tmpImageFile = File.createTempFile("img", ".img"); FileOutputStream fos = new FileOutputStream(tmpImageFile); fos.write(bytes); fos.close(); log.debug("created tmp file: " + tmpImageFile.getAbsolutePath()); ImageInfo info = ensureFormatIsSupported(tmpImageFile, bytes, true); // In the absence of an exception, tmpImageFile now contains an image // Word will accept ContentTypeManager ctm = opcPackage.getContentTypeManager(); // Ensure the relationships part exists if (sourcePart.getRelationshipsPart() == null) { RelationshipsPart.createRelationshipsPartForPart(sourcePart); } String proposedRelId = sourcePart.getRelationshipsPart().getNextId(); String ext = info.getMimeType().substring(info.getMimeType().indexOf("/") + 1); // System.out.println(ext); BinaryPartAbstractImage imagePart = (BinaryPartAbstractImage) ctm.newPartForContentType( info.getMimeType(), createImageName(opcPackage, sourcePart, proposedRelId, ext), null); log.debug( "created part " + imagePart.getClass().getName() + " with name " + imagePart.getPartName().toString()); FileInputStream fis = new FileInputStream(tmpImageFile); imagePart.setBinaryData(fis); imagePart.rels.add(sourcePart.addTargetPart(imagePart, proposedRelId)); imagePart.setImageInfo(info); // Delete the tmp file // As per http://stackoverflow.com/questions/991489/i-cant-delete-a-file-in-java // the following 3 lines are necessary, at least on Win 7 x64 // Also reported on Win XP, but in my testing, the files were deleting OK anyway. fos = null; fis = null; if (Docx4jProperties.getProperty( "docx4j.openpackaging.parts.WordprocessingML.BinaryPartAbstractImage.TempFiles.ForceGC", true)) { System.gc(); } if (tmpImageFile.delete()) { log.debug(".. deleted " + tmpImageFile.getAbsolutePath()); } else { log.warn("Couldn't delete tmp file " + tmpImageFile.getAbsolutePath()); tmpImageFile.deleteOnExit(); // If that doesn't work, see "Clean Up Your Mess: Managing Temp Files in Java Apps" // at devx.com } return imagePart; }