mirror of
https://github.com/Alfresco/alfresco-community-repo.git
synced 2025-08-07 17:49:17 +00:00
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:
@@ -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();
|
||||
}
|
||||
}
|
||||
|
@@ -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);
|
||||
|
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
||||
|
@@ -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
|
||||
*/
|
||||
|
@@ -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,
|
||||
|
@@ -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)
|
||||
|
Reference in New Issue
Block a user