FIXED : ALF-10578: iPad uploaded files appear upside down in share preview

Defaults to auto-orient images based on EXIF info


git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@32539 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
Gethin James
2011-12-05 14:46:57 +00:00
parent f8c8ea163a
commit 71e8e4ba85
8 changed files with 185 additions and 30 deletions

View File

@@ -166,4 +166,15 @@ public class ImageCropOptions
{
return this.gravity;
}
@Override
public String toString()
{
StringBuilder builder = new StringBuilder();
builder.append("ImageCropOptions [height=").append(this.height).append(", width=").append(this.width)
.append(", xOffset=").append(this.xOffset).append(", yOffset=").append(this.yOffset)
.append(", isPercentageCrop=").append(this.isPercentageCrop).append(", gravity=")
.append(this.gravity).append("]");
return builder.toString();
}
}

View File

@@ -154,6 +154,10 @@ public class ImageMagickContentTransformerWorker extends AbstractImageMagickCont
ImageCropOptions cropOptions = imageOptions.getCropOptions();
ImageResizeOptions resizeOptions = imageOptions.getResizeOptions();
String commandOptions = imageOptions.getCommandOptions();
if (imageOptions.isAutoOrient())
{
commandOptions = commandOptions + " -auto-orient";
}
if (cropOptions != null)
{
commandOptions = commandOptions + " " + getImageCropCommandOptions(cropOptions);

View File

@@ -114,4 +114,16 @@ public class ImageResizeOptions
{
return allowEnlargement;
}
@Override
public String toString()
{
StringBuilder builder = new StringBuilder();
builder.append("ImageResizeOptions [width=").append(this.width).append(", height=").append(this.height)
.append(", maintainAspectRatio=").append(this.maintainAspectRatio).append(", percentResize=")
.append(this.percentResize).append(", resizeToThumbnail=").append(this.resizeToThumbnail)
.append(", allowEnlargement=").append(this.allowEnlargement).append("]");
return builder.toString();
}
}

View File

@@ -33,7 +33,8 @@ public class ImageTransformationOptions extends TransformationOptions
public static final String OPT_COMMAND_OPTIONS = "commandOptions";
public static final String OPT_IMAGE_RESIZE_OPTIONS = "imageResizeOptions";
public static final String OPT_IMAGE_CROP_OPTIONS = "imageCropOptions";
public static final String OPT_IMAGE_AUTO_ORIENTATION = "imageAutoOrient";
/** Command string options, provided for backward compatibility */
private String commandOptions = "";
@@ -43,6 +44,7 @@ public class ImageTransformationOptions extends TransformationOptions
/** Image crop options */
private ImageCropOptions cropOptions;
private boolean autoOrient = true;
/**
* Set the command string options
*
@@ -86,13 +88,11 @@ public class ImageTransformationOptions extends TransformationOptions
@Override
public String toString()
{
StringBuilder msg = new StringBuilder(100);
msg.append(this.getClass().getSimpleName())
.append("[ commandOptions=").append(commandOptions)
.append(", resizeOptions=").append(resizeOptions)
.append("]");
return msg.toString();
StringBuilder builder = new StringBuilder();
builder.append("ImageTransformationOptions [commandOptions=").append(this.commandOptions)
.append(", resizeOptions=").append(this.resizeOptions).append(", cropOptions=")
.append(this.cropOptions).append(", autoOrient=").append(this.autoOrient).append("]");
return builder.toString();
}
/**
@@ -106,6 +106,7 @@ public class ImageTransformationOptions extends TransformationOptions
props.put(OPT_COMMAND_OPTIONS, commandOptions);
props.put(OPT_IMAGE_RESIZE_OPTIONS, resizeOptions);
props.put(OPT_IMAGE_CROP_OPTIONS, cropOptions);
props.put(OPT_IMAGE_AUTO_ORIENTATION, autoOrient);
return props;
}
@@ -125,4 +126,21 @@ public class ImageTransformationOptions extends TransformationOptions
{
return this.cropOptions;
}
/**
* @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;
}
}

View File

@@ -21,6 +21,7 @@ package org.alfresco.repo.rendition;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.net.URL;
@@ -81,6 +82,8 @@ import org.springframework.context.ConfigurableApplicationContext;
@SuppressWarnings("deprecation")
public class RenditionServiceIntegrationTest extends BaseAlfrescoSpringTest
{
private static final String WHITE = "ffffff";
private static final String BLACK = "000000";
private final static QName REFORMAT_RENDER_DEFN_NAME = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI,
ReformatRenderingEngine.NAME + System.currentTimeMillis());
private final static QName RESCALE_RENDER_DEFN_NAME = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI,
@@ -146,19 +149,12 @@ public class RenditionServiceIntegrationTest extends BaseAlfrescoSpringTest
// Create a test image
this.nodeWithImageContent = createContentNode(companyHome, "testImageNode");
// Stream some well-known image content into the node.
this.nodeWithImageContent = createContentNode(companyHome, "testImageNode");
// Stream some well-known image content into the node.
URL url = RenditionServiceIntegrationTest.class.getClassLoader().getResource("images/gray21.512.png");
assertNotNull("url of test image was null", url);
File imageFile = new File(url.getFile());
assertTrue(imageFile.exists());
nodeService.setProperty(nodeWithImageContent, ContentModel.PROP_CONTENT, new ContentData(null,
MimetypeMap.MIMETYPE_IMAGE_PNG, 0L, null));
writer = contentService.getWriter(nodeWithImageContent, ContentModel.PROP_CONTENT, true);
writer.setMimetype(MimetypeMap.MIMETYPE_IMAGE_PNG);
writer.setEncoding("UTF-8");
writer.putContent(imageFile);
setImageContentOnNode(nodeWithImageContent, MimetypeMap.MIMETYPE_IMAGE_PNG, imageFile);
// Create a test template node.
this.nodeWithFreeMarkerContent = createFreeMarkerNode(companyHome);
@@ -168,6 +164,17 @@ public class RenditionServiceIntegrationTest extends BaseAlfrescoSpringTest
{
return createNode(companyHome, name, ContentModel.TYPE_CONTENT);
}
private void setImageContentOnNode(NodeRef nodeWithImage, String mimetypeImage, File imageFile)
{
assertTrue(imageFile.exists());
nodeService.setProperty(nodeWithImage, ContentModel.PROP_CONTENT, new ContentData(null,
mimetypeImage, 0L, null));
ContentWriter writer = contentService.getWriter(nodeWithImage, ContentModel.PROP_CONTENT, true);
writer.setMimetype(mimetypeImage);
writer.setEncoding("UTF-8");
writer.putContent(imageFile);
}
private NodeRef createFreeMarkerNode(NodeRef companyHome)
{
@@ -649,7 +656,7 @@ public class RenditionServiceIntegrationTest extends BaseAlfrescoSpringTest
parameterValues.put(ImageRenderingEngine.PARAM_CROP_HEIGHT, imageNewYSize);
ImageTransformationOptions imageTransformationOptions = new ImageTransformationOptions();
final NodeRef newRenditionNode = performImageRendition(parameterValues);
final NodeRef newRenditionNode = performImageRendition(parameterValues,nodeWithImageContent);
// Assert that the rendition is of the correct size and has reasonable
// content.
@@ -704,7 +711,7 @@ public class RenditionServiceIntegrationTest extends BaseAlfrescoSpringTest
parameterValues.put(ImageRenderingEngine.PARAM_CROP_HEIGHT, 25); // 128 pixels
parameterValues.put(ImageRenderingEngine.PARAM_IS_PERCENT_CROP, true);
final NodeRef secondRenditionNode = performImageRendition(parameterValues);
final NodeRef secondRenditionNode = performImageRendition(parameterValues,nodeWithImageContent);
// Assert that the rendition is of the correct size and has reasonable
// content.
@@ -772,7 +779,7 @@ public class RenditionServiceIntegrationTest extends BaseAlfrescoSpringTest
parameterValues.put(ImageRenderingEngine.PARAM_RESIZE_WIDTH, imageNewXSize);
parameterValues.put(ImageRenderingEngine.PARAM_RESIZE_HEIGHT, imageNewYSize);
final NodeRef newRenditionNode = performImageRendition(parameterValues);
final NodeRef newRenditionNode = performImageRendition(parameterValues, nodeWithImageContent);
// Assert that the rendition is of the correct size and has reasonable
// content.
@@ -802,11 +809,11 @@ public class RenditionServiceIntegrationTest extends BaseAlfrescoSpringTest
// The upper left pixel of the image should be pure black.
int rgbAtTopLeft = img.getRGB(1, 1);
assertTrue("Incorrect image content.", Integer.toHexString(rgbAtTopLeft).endsWith("000000"));
assertTrue("Incorrect image content.", Integer.toHexString(rgbAtTopLeft).endsWith(BLACK));
// The lower right pixel of the image should be pure white
int rgbAtBottomRight = img.getRGB(img.getWidth() - 1, img.getHeight() - 1);
assertTrue("Incorrect image content.", Integer.toHexString(rgbAtBottomRight).endsWith("ffffff"));
assertTrue("Incorrect image content.", Integer.toHexString(rgbAtBottomRight).endsWith(WHITE));
return null;
}
@@ -818,7 +825,7 @@ public class RenditionServiceIntegrationTest extends BaseAlfrescoSpringTest
parameterValues.put(ImageRenderingEngine.PARAM_RESIZE_HEIGHT, 200);
parameterValues.put(ImageRenderingEngine.PARAM_IS_PERCENT_RESIZE, true);
final NodeRef secondRenditionNode = performImageRendition(parameterValues);
final NodeRef secondRenditionNode = performImageRendition(parameterValues, nodeWithImageContent);
// Assert that the rendition is of the correct size and has reasonable
// content.
@@ -848,11 +855,11 @@ public class RenditionServiceIntegrationTest extends BaseAlfrescoSpringTest
// The upper left pixel of the image should be pure black.
int rgbAtTopLeft = img.getRGB(1, 1);
assertTrue("Incorrect image content.", Integer.toHexString(rgbAtTopLeft).endsWith("000000"));
assertTrue("Incorrect image content.", Integer.toHexString(rgbAtTopLeft).endsWith(BLACK));
// The lower right pixel of the image should be pure white
int rgbAtBottomRight = img.getRGB(img.getWidth() - 1, img.getHeight() - 1);
assertTrue("Incorrect image content.", Integer.toHexString(rgbAtBottomRight).endsWith("ffffff"));
assertTrue("Incorrect image content.", Integer.toHexString(rgbAtBottomRight).endsWith(WHITE));
return null;
}
@@ -905,11 +912,11 @@ public class RenditionServiceIntegrationTest extends BaseAlfrescoSpringTest
// The upper left pixel of the image should be pure black.
int rgbAtTopLeft = img.getRGB(1, 1);
assertTrue("Incorrect image content.", Integer.toHexString(rgbAtTopLeft).endsWith("000000"));
assertTrue("Incorrect image content.", Integer.toHexString(rgbAtTopLeft).endsWith(BLACK));
// The lower right pixel of the image should be pure white
int rgbAtBottomRight = img.getRGB(img.getWidth() - 1, img.getHeight() - 1);
assertTrue("Incorrect image content.", Integer.toHexString(rgbAtBottomRight).endsWith("ffffff"));
assertTrue("Incorrect image content.", Integer.toHexString(rgbAtBottomRight).endsWith(WHITE));
return null;
}
@@ -1175,7 +1182,7 @@ public class RenditionServiceIntegrationTest extends BaseAlfrescoSpringTest
});
}
private NodeRef performImageRendition(final Map<String, Serializable> parameterValues)
protected NodeRef performImageRendition(final Map<String, Serializable> parameterValues, final NodeRef imageToRender)
{
final NodeRef newRenditionNode = transactionHelper
.doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback<NodeRef>()
@@ -1197,7 +1204,7 @@ public class RenditionServiceIntegrationTest extends BaseAlfrescoSpringTest
action.setParameterValue(s, parameterValues.get(s));
}
ChildAssociationRef renditionAssoc = renditionService.render(nodeWithImageContent, action);
ChildAssociationRef renditionAssoc = renditionService.render(imageToRender, action);
validateRenditionAssociation(renditionAssoc, RESCALE_RENDER_DEFN_NAME);
@@ -2218,6 +2225,90 @@ public class RenditionServiceIntegrationTest extends BaseAlfrescoSpringTest
this.scriptService.executeScript(location, model);
}
/**
* This test method takes an image with Exif Orientation information and checks if it is automatially rotated.
*/
public void testAutoRotateImage() throws Exception
{
NodeRef companyHome = repositoryHelper.getCompanyHome();
//Check image is there
String imageSource = "images/rotated_gray21.512.jpg";
File imageFile = retrieveValidImageFile(imageSource);
//Create node and save contents
final NodeRef newNodeForRotate = createNode(companyHome, "rotateImageNode", ContentModel.TYPE_CONTENT);
setImageContentOnNode(newNodeForRotate, MimetypeMap.MIMETYPE_IMAGE_JPEG, imageFile);
//Test auto rotates
final Map<String, Serializable> parameterValues = new HashMap<String, Serializable>();
resizeImageAndCheckOrientation(newNodeForRotate, parameterValues, BLACK, WHITE);
//Test doesn't auto rotate
parameterValues.clear();
parameterValues.put(ImageRenderingEngine.PARAM_AUTO_ORIENTATION, false);
resizeImageAndCheckOrientation(newNodeForRotate, parameterValues, WHITE, BLACK);
//Clean up
nodeService.deleteNode(newNodeForRotate);
}
private void resizeImageAndCheckOrientation(final NodeRef nodeToResize, final Map<String, Serializable> parameterValues, final String topLeft, final String bottomRight)
{
//Resize to 100 by 100
final Integer imageNewXSize = new Integer(100);
final Integer imageNewYSize = new Integer(100);
parameterValues.put(ImageRenderingEngine.PARAM_RESIZE_WIDTH, imageNewXSize);
parameterValues.put(ImageRenderingEngine.PARAM_RESIZE_HEIGHT, imageNewYSize);
final NodeRef newRenditionNode = performImageRendition(parameterValues, nodeToResize);
// Assert that the rendition is of the correct size and has reasonable content.
transactionHelper.doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback<Void>()
{
public Void execute() throws Throwable
{
// The rescaled image rendition is a child of the original test
// node.
List<ChildAssociationRef> children = nodeService.getChildAssocs(nodeToResize,
new RegexQNamePattern(getLongNameWithEscapedBraces(RenditionModel.ASSOC_RENDITION)),
new RegexQNamePattern(getLongNameWithEscapedBraces(RESCALE_RENDER_DEFN_NAME)));
// There should only be one child of the image node: the
// rendition we've just created.
assertEquals("Unexpected number of children", 1, children.size());
NodeRef newImageRendition = children.get(0).getChildRef();
assertEquals(newRenditionNode, newImageRendition);
ContentReader reader = contentService.getReader(newImageRendition, ContentModel.PROP_CONTENT);
assertNotNull("Reader to rendered image was null", reader);
BufferedImage srcImg = ImageIO.read(reader.getContentInputStream());
checkTopLeftBottomRight(srcImg, topLeft, bottomRight);
return null;
}
});
}
private static File retrieveValidImageFile(String imageSource) throws IOException
{
URL url = RenditionServiceIntegrationTest.class.getClassLoader().getResource(imageSource);
File imageFile = new File(url.getFile());
BufferedImage img = ImageIO.read(url);
assertNotNull("image was null", img);
checkTopLeftBottomRight(img, WHITE, BLACK);
return imageFile;
}
private static void checkTopLeftBottomRight(BufferedImage img, String topLeft, String bottomRight)
{
int rgbAtTopLeft = img.getRGB(1, 1);
assertTrue("upper left should be "+topLeft, Integer.toHexString(rgbAtTopLeft).endsWith(topLeft));
int rgbAtBottomRight = img.getRGB(img.getWidth() - 1, img.getHeight() - 1);
assertTrue("lower right should be "+bottomRight, Integer.toHexString(rgbAtBottomRight).endsWith(bottomRight));
}
/**
* A dummy rendering engine used in testing
*/

View File

@@ -229,6 +229,17 @@ public class ImageRenderingEngine extends AbstractTransformationRenderingEngine
*/
public static final String PARAM_COMMAND_OPTIONS = "commandOptions";
/**
* This optional {@link Boolean} flag parameter specifies if the engine should
* automatically rotate and image based on the EXIF orientation flag. If
* this parameter is set to <code>true</code> then the engine reads
* and resets the EXIF image profile setting 'Orientation' and then performs
* the appropriate 90 degree rotation on the image to orient the image,
* for correct viewing.
* This parameter defaults to <code>true</code>.
*/
public static final String PARAM_AUTO_ORIENTATION = "autoOrientation";
/*
* @seeorg.alfresco.repo.rendition.executer.ReformatRenderingEngine#
* getTransformOptions
@@ -242,9 +253,12 @@ public class ImageRenderingEngine extends AbstractTransformationRenderingEngine
ImageResizeOptions imageResizeOptions = getImageResizeOptions(context);
ImageCropOptions cropOptions = getImageCropOptions(context);
boolean autoOrient = context.getParamWithDefault(PARAM_AUTO_ORIENTATION, true);
ImageTransformationOptions imageTransformationOptions = new ImageTransformationOptions();
imageTransformationOptions.setResizeOptions(imageResizeOptions);
imageTransformationOptions.setCropOptions(cropOptions);
imageTransformationOptions.setAutoOrient(autoOrient);
if (commandOptions != null)
{
imageTransformationOptions.setCommandOptions(commandOptions);
@@ -359,6 +373,10 @@ public class ImageRenderingEngine extends AbstractTransformationRenderingEngine
protected Collection<ParameterDefinition> getParameterDefinitions()
{
Collection<ParameterDefinition> paramList = super.getParameterDefinitions();
//Orientation
paramList.add(new ParameterDefinitionImpl(PARAM_AUTO_ORIENTATION, DataTypeDefinition.BOOLEAN, false,
getParamDisplayLabel(PARAM_AUTO_ORIENTATION)));
//Resize Params
paramList.add(new ParameterDefinitionImpl(PARAM_RESIZE_WIDTH, DataTypeDefinition.INT, false,

View File

@@ -153,6 +153,7 @@ public class ThumbnailRenditionConvertor
{
ImageTransformationOptions imTransformationOptions = (ImageTransformationOptions)transformationOptions;
putParameterIfNotNull(ImageRenderingEngine.PARAM_COMMAND_OPTIONS, imTransformationOptions.getCommandOptions(), parameters);
putParameterIfNotNull(ImageRenderingEngine.PARAM_AUTO_ORIENTATION, imTransformationOptions.isAutoOrient(), parameters);
ImageResizeOptions imgResizeOptions = imTransformationOptions.getResizeOptions();
if (imgResizeOptions != null)