I need to transform PDF (scanned documents) to jpg image for preview using thumbnails. The default output is barely readable.I've found that you can get better output if you useconvert -density 300 source.pdf -quality 80 target.jpg
For the standard ImageMagick thirdparty subsystem you can add command options after "source.pdf", but I also need parameters <strong>before</strong>.So I set out to extend the ImageMagick subsystem to allow for this parameter to be added.Created two new classes with the added options, ExtendedImageMagickContentTransformerWorker and ExtendedImageTransformationOptions.They ar mostly the same as the original classes, with the inputOptions added.I wanted the ExtendedImageMagickContentTransformerWorker to support both ExtendedImageTransformationOptions and ImageTransformationOptions so that existing thumbnails didn't have to be redefined.The bean for ImageMagick in thirdparty subsystem is changed, and it works well for existing thumbnails that use ImageTransformationOptions, but for my thumbnail that use ExtendedImageTransformationOptions i get the error below.<strong>Why do I get "No bean named '' is defined" error? Should i look in java source or bean?</strong>When I change <bean parent="defaultExtendedImageTransformationOptions">
to <bean parent="defaultmageTransformationOptions">
it works again (and that still uses ExtendedImageMagickContentTransformerWorker).EDIT: Sorry about the length of the post, but code sections ar supposed to display as blocks, that doesn't work.<code title="Error when getting thumbnail">org.springframework.beans.factory.NoSuchBeanDefinitionException - No bean named '' is defined org.springframework.beans.factory.support.DefaultListableBeanFactory.getBeanDefinition(DefaultListableBeanFactory.java:527)org.springframework.beans.factory.support.AbstractBeanFactory.getMergedLocalBeanDefinition(AbstractBeanFactory.java:1083)org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:274)org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:190)org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1075)org.alfresco.repo.action.ActionServiceImpl.directActionExecution(ActionServiceImpl.java:837)org.alfresco.repo.action.ActionServiceImpl.executeActionImpl(ActionServiceImpl.java:738)org.alfresco.repo.action.ActionServiceImpl.executeAction(ActionServiceImpl.java:572)</code><code title="Thumbnail bean definition" language="xml" > <bean id="defaultExtendedImageTransformationOptions" class="se.loftux.repo.content.transform.magick.ExtendedImageTransformationOptions" abstract="true"> <property name="timeoutMs" value="${system.thumbnail.definition.default.timeoutMs}" /> <property name="readLimitTimeMs" value="${system.thumbnail.definition.default.readLimitTimeMs}" /> <property name="maxSourceSizeKBytes" value="${system.thumbnail.definition.default.maxSourceSizeKBytes}" /> <property name="readLimitKBytes" value="${system.thumbnail.definition.default.readLimitKBytes}" /> <property name="pageLimit" value="${system.thumbnail.definition.default.pageLimit}" /> <property name="maxPages" value="${system.thumbnail.definition.default.maxPages}" /> </bean> <bean id="thumbnailTAMArkivOverlaypreview.registry" class="org.alfresco.repo.thumbnail.ThumbnailDefinitionSpringRegisterer"> <property name="thumbnailRegistry" ref="thumbnailRegistry" /> <property name="thumbnailDefinition"> <bean id="thumbnailTAMArkivOverlaypreview" class="org.alfresco.repo.thumbnail.ThumbnailDefinition"> <property name="name" value="overlay_preview" /> <property name="mimetype" value="image/jpeg"/> <property name="transformationOptions"> <bean parent="defaultExtendedImageTransformationOptions"> <property name="resizeOptions"> <bean class="org.alfresco.repo.content.transform.magick.ImageResizeOptions"> <property name="height" value="700"/> <property name="width" value="630"/> <property name="allowEnlargement" value="true" /> <property name="maintainAspectRatio" value="true" /> <property name="resizeToThumbnail" value="true" /> </bean> </property> <property name="commandOptions"> <value>-quality 80</value> </property> <property name="inputCommandOptions"> <value>-density 300</value> </property> </bean> </property> <property name="placeHolderResourcePath" value="alfresco/thumbnail/thumbnail_placeholder_630.jpg" /> <property name="runAs" value="System"/> <property name="failureHandlingOptions" ref="standardFailureOptions"/> </bean> </property> </bean></code><code title="ExtendedImageMagickContentTransformerWorker" language="java" >package se.loftux.repo.content.transform.magick;import java.io.File;import java.util.HashMap;import java.util.Map;import org.alfresco.error.AlfrescoRuntimeException;import org.alfresco.repo.content.MimetypeMap;import org.alfresco.repo.content.transform.magick.ImageResizeOptions;import org.alfresco.repo.content.transform.magick.ImageTransformationOptions;import org.alfresco.repo.content.transform.magick.AbstractImageMagickContentTransformerWorker;import org.alfresco.service.cmr.repository.ContentIOException;import org.alfresco.service.cmr.repository.CropSourceOptions;import org.alfresco.service.cmr.repository.PagedSourceOptions;import org.alfresco.service.cmr.repository.TransformationOptions;import org.alfresco.util.exec.RuntimeExec;import org.alfresco.util.exec.RuntimeExec.ExecutionResult;import org.apache.commons.logging.Log;import org.apache.commons.logging.LogFactory;import se.loftux.repo.content.transform.magick.ExtendedImageTransformationOptions;/** * Executes a statement to implement * * @author Derek Hulley * Extended by peter on 2014-02-19. */public class ExtendedImageMagickContentTransformerWorker extends AbstractImageMagickContentTransformerWorker{ /** Loftux input options variable name */ private static final String KEY_INPUT_OPTIONS = "inputOptions"; /** options variable name */ private static final String KEY_OPTIONS = "options"; /** source variable name */ private static final String VAR_SOURCE = "source"; /** target variable name */ private static final String VAR_TARGET = "target"; private static final Log logger = LogFactory.getLog(ExtendedImageMagickContentTransformerWorker.class); /** the system command executer */ private RuntimeExec executer; /** the check command executer */ private RuntimeExec checkCommand; /** the output from the check command */ private String versionString; /** * Default constructor */ public ExtendedImageMagickContentTransformerWorker() { // Intentionally empty } /** * @param executer the system command executer */ public void setExecuter(RuntimeExec executer) { this.executer = executer; } /** * Sets the command that must be executed in order to retrieve version information from the converting executable * and thus test that the executable itself is present. * * @param checkCommand * command executer to retrieve version information */ public void setCheckCommand(RuntimeExec checkCommand) { this.checkCommand = checkCommand; } /** * Gets the version string captured from the check command. * * @return the version string */ public String getVersionString() { return this.versionString; } /** * Checks for the JMagick and ImageMagick dependencies, using the common * {@link #transformInternal(java.io.File, java.io.File) transformation method} to check * that the sample image can be converted. */ @Override public void afterPropertiesSet() { if (executer == null) { throw new AlfrescoRuntimeException("System runtime executer not set"); } super.afterPropertiesSet(); if (isAvailable()) { try { // On some platforms / versions, the -version command seems to return an error code whilst still // returning output, so let's not worry about the exit code! ExecutionResult result = this.checkCommand.execute(); this.versionString = result.getStdOut().trim(); } catch (Throwable e) { setAvailable(false); logger.error(getClass().getSimpleName() + " not available: " + (e.getMessage() != null ? e.getMessage() : "")); // debug so that we can trace the issue if required logger.debug(e); } } } /** * Transform the image content from the source file to the target file */ @Override protected void transformInternal(File sourceFile, String sourceMimetype, File targetFile, String targetMimetype, TransformationOptions options) throws Exception { Map<String, String> properties = new HashMap<String, String>(5); // set properties if (options instanceof ImageTransformationOptions) { ImageTransformationOptions imageOptions = (ImageTransformationOptions)options; CropSourceOptions cropOptions = imageOptions.getSourceOptions(CropSourceOptions.class); ImageResizeOptions resizeOptions = imageOptions.getResizeOptions(); String commandOptions = imageOptions.getCommandOptions(); if (commandOptions == null) { commandOptions = ""; } if (imageOptions.isAutoOrient()) { commandOptions = commandOptions + " -auto-orient"; } if (cropOptions != null) { commandOptions = commandOptions + " " + getImageCropCommandOptions(cropOptions); } if (resizeOptions != null) { commandOptions = commandOptions + " " + getImageResizeCommandOptions(resizeOptions); } properties.put(KEY_OPTIONS, commandOptions); } // Loftux - Check for if (options instanceof ExtendedImageTransformationOptions) { ExtendedImageTransformationOptions extendedImageOptions = (ExtendedImageTransformationOptions)options; String inputCommandOptions = extendedImageOptions.getInputCommandOptions(); CropSourceOptions cropOptions = extendedImageOptions.getSourceOptions(CropSourceOptions.class); ImageResizeOptions resizeOptions = extendedImageOptions.getResizeOptions(); String commandOptions = extendedImageOptions.getCommandOptions(); if (inputCommandOptions == null) { inputCommandOptions = ""; } if (commandOptions == null) { commandOptions = ""; } if (extendedImageOptions.isAutoOrient()) { commandOptions = commandOptions + " -auto-orient"; } if (cropOptions != null) { commandOptions = commandOptions + " " + getImageCropCommandOptions(cropOptions); } if (resizeOptions != null) { commandOptions = commandOptions + " " + getImageResizeCommandOptions(resizeOptions); } properties.put(KEY_OPTIONS, commandOptions); properties.put(KEY_INPUT_OPTIONS, inputCommandOptions); } else { properties.put(KEY_INPUT_OPTIONS, ""); } properties.put(VAR_SOURCE, sourceFile.getAbsolutePath() + getSourcePageRange(options, sourceMimetype, targetMimetype)); properties.put(VAR_TARGET, targetFile.getAbsolutePath()); // execute the statement long timeoutMs = options.getTimeoutMs(); RuntimeExec.ExecutionResult result = executer.execute(properties, timeoutMs); if (result.getExitValue() != 0 && result.getStdErr() != null && result.getStdErr().length() > 0) { throw new ContentIOException("Failed to perform ImageMagick transformation: \n" + result); } // success if (logger.isDebugEnabled()) { logger.debug("Loftux Extended ImageMagic executed successfully: \n" + executer); } } /** * Gets the imagemagick command string for the image crop options provided * * @param imageResizeOptions image resize options * @return String the imagemagick command options */ private String getImageCropCommandOptions(CropSourceOptions cropOptions) { StringBuilder builder = new StringBuilder(32); String gravity = cropOptions.getGravity(); if(gravity!=null) { builder.append("-gravity "); builder.append(gravity); builder.append(" "); } builder.append("-crop "); int width = cropOptions.getWidth(); if (width > -1) { builder.append(width); } int height = cropOptions.getHeight(); if (height > -1) { builder.append("x"); builder.append(height); } if (cropOptions.isPercentageCrop()) { builder.append("%"); } appendOffset(builder, cropOptions.getXOffset()); appendOffset(builder, cropOptions.getYOffset()); builder.append(" +repage"); return builder.toString(); } /** * @param builder * @param xOffset */ private void appendOffset(StringBuilder builder, int xOffset) { if(xOffset>=0) { builder.append("+"); } builder.append(xOffset); } /** * Gets the imagemagick command string for the image resize options provided * * @param imageResizeOptions image resize options * @return String the imagemagick command options */ private String getImageResizeCommandOptions(ImageResizeOptions imageResizeOptions) { StringBuilder builder = new StringBuilder(32); // These are ImageMagick options. See http://www.imagemagick.org/script/command-line-processing.php#geometry for details. if (imageResizeOptions.isResizeToThumbnail() == true) { builder.append("-thumbnail "); } else { builder.append("-resize "); } if (imageResizeOptions.getWidth() > -1) { builder.append(imageResizeOptions.getWidth()); } if (imageResizeOptions.getHeight() > -1) { builder.append("x"); builder.append(imageResizeOptions.getHeight()); } if (imageResizeOptions.isPercentResize() == true) { builder.append("%"); } // ALF-7308. Disallow the enlargement of small images e.g. within imgpreview thumbnail. if (!imageResizeOptions.getAllowEnlargement()) { builder.append(">"); } if (imageResizeOptions.isMaintainAspectRatio() == false) { builder.append("!"); } return builder.toString(); } /** * Determines whether or not a single page range is required for the given source and target mimetypes. * * @param sourceMimetype * @param targetMimetype * @return whether or not a page range must be specified for the transformer to read the target files */ private boolean isSingleSourcePageRangeRequired(String sourceMimetype, String targetMimetype) { // Need a page source if we're transforming from PDF or TIFF to an image other than TIFF // or from PSD return ((sourceMimetype.equals(MimetypeMap.MIMETYPE_PDF) || sourceMimetype.equals(MimetypeMap.MIMETYPE_IMAGE_TIFF)) && ((!targetMimetype.equals(MimetypeMap.MIMETYPE_IMAGE_TIFF) && targetMimetype.contains(MIMETYPE_IMAGE_PREFIX)) || targetMimetype.equals(MimetypeMap.MIMETYPE_APPLICATION_PHOTOSHOP) || targetMimetype.equals(MimetypeMap.MIMETYPE_APPLICATION_EPS)) || sourceMimetype.equals(MimetypeMap.MIMETYPE_APPLICATION_PHOTOSHOP)); } /** * Gets the page range from the source to use in the command line. * * @param options the transformation options * @param sourceMimetype the source mimetype * @param targetMimetype the target mimetype * @return the source page range for the command line */ private String getSourcePageRange(TransformationOptions options, String sourceMimetype, String targetMimetype) { // Check for PagedContentSourceOptions in the options if (options instanceof ImageTransformationOptions) { ImageTransformationOptions imageOptions = (ImageTransformationOptions) options; PagedSourceOptions pagedSourceOptions = imageOptions.getSourceOptions(PagedSourceOptions.class); if (pagedSourceOptions != null) { if (pagedSourceOptions.getStartPageNumber() != null && pagedSourceOptions.getEndPageNumber() != null) { if (pagedSourceOptions.getStartPageNumber().equals(pagedSourceOptions.getEndPageNumber())) { return "[" + (pagedSourceOptions.getStartPageNumber() - 1) + "]"; } else { if (isSingleSourcePageRangeRequired(sourceMimetype, targetMimetype)) { throw new AlfrescoRuntimeException( "A single page is required for targets of type " + targetMimetype); } return "[" + (pagedSourceOptions.getStartPageNumber() - 1) + "-" + (pagedSourceOptions.getEndPageNumber() - 1) + "]"; } } else { // TODO specified start to end of doc and start of doc to specified end not yet supported // Just grab a single page specified by either start or end if (pagedSourceOptions.getStartPageNumber() != null) return "[" + (pagedSourceOptions.getStartPageNumber() - 1) + "]"; if (pagedSourceOptions.getEndPageNumber() != null) return "[" + (pagedSourceOptions.getEndPageNumber() - 1) + "]"; } } } if (options instanceof ExtendedImageTransformationOptions) { ExtendedImageTransformationOptions extendedImageOptions = (ExtendedImageTransformationOptions) options; PagedSourceOptions pagedSourceOptions = extendedImageOptions.getSourceOptions(PagedSourceOptions.class); if (pagedSourceOptions != null) { if (pagedSourceOptions.getStartPageNumber() != null && pagedSourceOptions.getEndPageNumber() != null) { if (pagedSourceOptions.getStartPageNumber().equals(pagedSourceOptions.getEndPageNumber())) { return "[" + (pagedSourceOptions.getStartPageNumber() - 1) + "]"; } else { if (isSingleSourcePageRangeRequired(sourceMimetype, targetMimetype)) { throw new AlfrescoRuntimeException( "A single page is required for targets of type " + targetMimetype); } return "[" + (pagedSourceOptions.getStartPageNumber() - 1) + "-" + (pagedSourceOptions.getEndPageNumber() - 1) + "]"; } } else { // TODO specified start to end of doc and start of doc to specified end not yet supported // Just grab a single page specified by either start or end if (pagedSourceOptions.getStartPageNumber() != null) return "[" + (pagedSourceOptions.getStartPageNumber() - 1) + "]"; if (pagedSourceOptions.getEndPageNumber() != null) return "[" + (pagedSourceOptions.getEndPageNumber() - 1) + "]"; } } } if (options.getPageLimit() == 1 || isSingleSourcePageRangeRequired(sourceMimetype, targetMimetype)) { return "[0]"; } else { return ""; } }}</code><code title="ExtendedImageTransformationOptions" language="java" >package se.loftux.repo.content.transform.magick;import java.util.HashMap;import java.util.Map;import org.alfresco.repo.content.transform.magick.ImageResizeOptions;import org.alfresco.service.cmr.repository.TransformationOptions;import org.alfresco.service.cmr.repository.TransformationSourceOptions;/** * Image transformation options * * @author Roy Wetherall * Extended by peter on 2014-02-18. */public class ExtendedImageTransformationOptions extends TransformationOptions{ // Loftux - New inputCommand options public static final String OPT_INPUT_COMMAND_OPTIONS = "inputCommandOptions"; public static final String OPT_COMMAND_OPTIONS = "commandOptions"; public static final String OPT_IMAGE_RESIZE_OPTIONS = "imageResizeOptions"; public static final String OPT_IMAGE_AUTO_ORIENTATION = "imageAutoOrient"; /** Loftux inputCommand string options, provided to set input file options */ private String inputCommandOptions = ""; /** Command string options, provided for backward compatibility */ private String commandOptions = ""; /** Image resize options */ private ImageResizeOptions resizeOptions; private boolean autoOrient = true; /** * Set the pre-command string options * Loftux * @param inputCommandOptions the command string options */ public void setInputCommandOptions(String inputCommandOptions) { this.inputCommandOptions = inputCommandOptions; } /** * Get the pre-command string options * Loftux * @return String the command string options */ public String getInputCommandOptions() { return inputCommandOptions; } /** * Set the command string options * * @param commandOptions the command string options */ public void setCommandOptions(String commandOptions) { this.commandOptions = commandOptions; } /** * Get the command string options * * @return String the command string options */ public String getCommandOptions() { return commandOptions; } /** * Set the image resize options * * @param resizeOptions image resize options */ public void setResizeOptions(ImageResizeOptions resizeOptions) { this.resizeOptions = resizeOptions; } /** * Get the image resize options * * @return ImageResizeOptions image resize options */ public ImageResizeOptions getResizeOptions() { return resizeOptions; } @Override public String toString() { StringBuilder builder = new StringBuilder(); builder.append("ExtendedImageTransformationOptions [inputCommandOptions=").append(this.inputCommandOptions) .append(", commandOptions=").append(this.commandOptions) .append(", resizeOptions=").append(this.resizeOptions) .append(", autoOrient=").append(this.autoOrient).append("]"); if (getSourceOptionsList() != null) { builder.append(", sourceOptions={ "); int i = 0; for (TransformationSourceOptions sourceOptions : getSourceOptionsList()) { builder.append((i != 0) ? " , ": ""); builder.append(sourceOptions.getClass().getSimpleName()) .append(sourceOptions.toString()); i++; } builder.append("} "); } builder.append("]"); return builder.toString(); } /** * Overrides the base class implementation to add our options */ @Override public Map<String, Object> toMap() { Map<String, Object> baseProps = super.toMap(); Map<String, Object> props = new HashMap<String, Object>(baseProps); props.put(OPT_INPUT_COMMAND_OPTIONS, inputCommandOptions); props.put(OPT_COMMAND_OPTIONS, commandOptions); props.put(OPT_IMAGE_RESIZE_OPTIONS, resizeOptions); props.put(OPT_IMAGE_AUTO_ORIENTATION, autoOrient); return props; } /** * @return Will the image be automatically oriented(rotated) based on the EXIF "Orientation" data. * Defaults to TRUE */ public boolean isAutoOrient() { return this.autoOrient; } /** * @param autoOrient automatically orient (rotate) based on the EXIF "Orientation" data */ public void setAutoOrient(boolean autoOrient) { this.autoOrient = autoOrient; } @Override public void copyFrom(TransformationOptions origOptions) { super.copyFrom(origOptions); if (origOptions != null) { if (origOptions instanceof ExtendedImageTransformationOptions) { // Clone ExtendedImageTransformationOptions this.setInputCommandOptions(((ExtendedImageTransformationOptions) origOptions).getInputCommandOptions()); this.setCommandOptions(((ExtendedImageTransformationOptions) origOptions).getCommandOptions()); this.setResizeOptions(((ExtendedImageTransformationOptions) origOptions).getResizeOptions()); this.setAutoOrient(((ExtendedImageTransformationOptions) origOptions).isAutoOrient()); } } }}</code>