Author: gagan
Date: Thu Feb 3 17:15:45 2011
New Revision: 1066875
URL: http://svn.apache.org/viewvc?rev=1066875&view=rev
Log:
Patch by satya3656 | Issue 3082041: Support for retaining subsampling parameter
for jpeg image rewrite | http://codereview.appspot.com/3082041/
Modified:
shindig/trunk/java/common/conf/shindig.properties
shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/image/BaseOptimizer.java
shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/image/BasicImageRewriter.java
shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/image/JPEGOptimizer.java
shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/image/OptimizerConfig.java
shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/rewrite/image/JPEGOptimizerTest.java
shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/rewrite/image/OptimizerConfigTest.java
Modified: shindig/trunk/java/common/conf/shindig.properties
URL:
http://svn.apache.org/viewvc/shindig/trunk/java/common/conf/shindig.properties?rev=1066875&r1=1066874&r2=1066875&view=diff
==============================================================================
--- shindig/trunk/java/common/conf/shindig.properties (original)
+++ shindig/trunk/java/common/conf/shindig.properties Thu Feb 3 17:15:45 2011
@@ -72,6 +72,7 @@ shindig.image-rewrite.max-palette-size =
shindig.image-rewrite.allow-jpeg-conversion = true
shindig.image-rewrite.jpeg-compression = 0.90
shindig.image-rewrite.min-threshold-bytes = 200
+shindig.image-rewrite.jpeg-retain-subsampling = false
# Huffman optimization reduces the images size by addition 4-6% without
# any loss in the quality of the image, but takes extra cpu cycles for
# computing the optimized huffman tables.
Modified:
shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/image/BaseOptimizer.java
URL:
http://svn.apache.org/viewvc/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/image/BaseOptimizer.java?rev=1066875&r1=1066874&r2=1066875&view=diff
==============================================================================
---
shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/image/BaseOptimizer.java
(original)
+++
shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/image/BaseOptimizer.java
Thu Feb 3 17:15:45 2011
@@ -22,10 +22,15 @@ import org.apache.sanselan.ImageWriteExc
import org.apache.sanselan.Sanselan;
import org.apache.shindig.gadgets.http.HttpResponseBuilder;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import com.google.common.base.Objects;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
-import com.google.common.base.Objects;
+import com.sun.imageio.plugins.jpeg.JPEG;
import com.sun.imageio.plugins.jpeg.JPEGImageWriter;
import java.awt.image.BufferedImage;
@@ -40,8 +45,9 @@ import javax.imageio.ImageIO;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.ImageWriteParam;
import javax.imageio.ImageWriter;
-import javax.imageio.plugins.jpeg.JPEGImageWriteParam;
+import javax.imageio.metadata.IIOInvalidTreeException;
import javax.imageio.metadata.IIOMetadata;
+import javax.imageio.plugins.jpeg.JPEGImageWriteParam;
/**
* Base class for image optimizers
@@ -59,13 +65,19 @@ abstract class BaseOptimizer {
protected ImageOutputter outputter;
protected byte[] minBytes;
protected int minLength;
+ protected JpegImageUtils.JpegImageParams sourceImageParams;
int reductionPct;
-
public BaseOptimizer(OptimizerConfig config, HttpResponseBuilder response) {
+ this(config, response, null);
+ }
+
+ public BaseOptimizer(OptimizerConfig config, HttpResponseBuilder response,
+ JpegImageUtils.JpegImageParams sourceImageParams) {
this.config = config;
this.response = response;
this.minLength = response.getContentLength();
+ this.sourceImageParams = sourceImageParams;
this.outputter = getOutputter();
}
@@ -82,7 +94,12 @@ abstract class BaseOptimizer {
config.getJpegHuffmanOptimization());
}
}
- return new ImageIOOutputter(writer, param);
+
+ JpegImageUtils.SamplingModes samplingMode =
JpegImageUtils.SamplingModes.DEFAULT;
+ if (config.getJpegRetainSubsampling() && sourceImageParams != null) {
+ samplingMode = sourceImageParams.getSamplingMode();
+ }
+ return new ImageIOOutputter(writer, param, samplingMode);
}
return new
SanselanOutputter(FORMAT_NAME_TO_IMAGE_FORMAT.get(getOriginalFormatName()));
}
@@ -94,6 +111,7 @@ abstract class BaseOptimizer {
if (image == null) {
return;
}
+
byte[] bytes = outputter.toBytes(image);
if (minLength > bytes.length) {
minBytes = bytes;
@@ -156,9 +174,17 @@ abstract class BaseOptimizer {
ImageWriter writer;
ByteArrayOutputStream baos;
ImageWriteParam writeParam;
+ JpegImageUtils.SamplingModes jpegSamplingMode;
+
public ImageIOOutputter(ImageWriter writer, ImageWriteParam writeParam) {
+ this(writer, writeParam, JpegImageUtils.SamplingModes.DEFAULT);
+ }
+
+ public ImageIOOutputter(ImageWriter writer, ImageWriteParam writeParam,
+ JpegImageUtils.SamplingModes jpegSamplingMode) {
this.writer = writer;
this.writeParam = Objects.firstNonNull(writeParam,
writer.getDefaultWriteParam());
+ this.jpegSamplingMode = jpegSamplingMode;
}
public byte[] toBytes(BufferedImage image) throws IOException {
@@ -185,11 +211,55 @@ abstract class BaseOptimizer {
IIOMetadata metadata = writer.getDefaultImageMetadata(
new ImageTypeSpecifier(image.getColorModel(),
image.getSampleModel()),
metaImageWriteParam);
+
+ if (jpegSamplingMode.getModeValue() > 0 && writer instanceof
JPEGImageWriter) {
+ setJpegSubsamplingMode(metadata);
+ }
+
writer.write(null, new IIOImage(image,
Collections.<BufferedImage>emptyList(), metadata),
metaImageWriteParam == null ? writeParam : null);
return baos.toByteArray();
}
+
+ private void setJpegSubsamplingMode(IIOMetadata metadata)
+ throws IIOInvalidTreeException {
+ // Tweaking the image metadata to override default subsampling(4:2:0)
with
+ // 4:4:4.
+ Node rootNode = metadata.getAsTree(JPEG.nativeImageMetadataFormatName);
+ boolean metadataUpdated = false;
+ // The top level root node has two children, out of which the second one
will
+ // contain all the information related to image markers.
+ if (rootNode.getLastChild() != null) {
+ Node markerNode = rootNode.getLastChild();
+ NodeList markers = markerNode.getChildNodes();
+ // Search for 'SOF' marker where subsampling information is stored.
+ for (int i = 0; i < markers.getLength(); i++) {
+ Node node = markers.item(i);
+ // 'SOF' marker can have
+ // 1 child node if the color representation is greyscale,
+ // 3 child nodes if the color representation is YCbCr, and
+ // 4 child nodes if the color representation is YCMK.
+ // This subsampling applies only to YCbCr.
+ if (node.getNodeName().equalsIgnoreCase("sof") &&
node.hasChildNodes() &&
+ node.getChildNodes().getLength() == 3) {
+ // In 'SOF' marker, first child corresponds to the luminance
channel, and setting
+ // the HsamplingFactor and VsamplingFactor to 1, will imply 4:4:4
chroma subsampling.
+ NamedNodeMap attrMap = node.getFirstChild().getAttributes();
+ int samplingMode = jpegSamplingMode.getModeValue();
+ attrMap.getNamedItem("HsamplingFactor").setNodeValue((samplingMode
& 0xf) + "");
+
attrMap.getNamedItem("VsamplingFactor").setNodeValue(((samplingMode >> 4) &
0xf) + "");
+ metadataUpdated = true;
+ break;
+ }
+ }
+ }
+
+ // Read the updated metadata from the metadata node tree.
+ if (metadataUpdated) {
+ metadata.setFromTree(JPEG.nativeImageMetadataFormatName, rootNode);
+ }
+ }
}
/**
Modified:
shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/image/BasicImageRewriter.java
URL:
http://svn.apache.org/viewvc/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/image/BasicImageRewriter.java?rev=1066875&r1=1066874&r2=1066875&view=diff
==============================================================================
---
shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/image/BasicImageRewriter.java
(original)
+++
shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/image/BasicImageRewriter.java
Thu Feb 3 17:15:45 2011
@@ -304,6 +304,11 @@ public class BasicImageRewriter implemen
return;
}
+ JpegImageUtils.JpegImageParams jpegImageParams = null;
+ if (imageFormat == ImageFormat.IMAGE_FORMAT_JPEG) {
+ jpegImageParams =
JpegImageUtils.getJpegImageData(response.getContentBytes(), uri.getPath());
+ }
+
// Step#1: Read the image using appropriate readers for the
corresponding image format.
BufferedImage image = readImage(imageFormat, response);
@@ -324,7 +329,7 @@ public class BasicImageRewriter implemen
// Step#5: Optimize the supported image formats viz PNG, GIF, JPG & BMP
using 'BaseOptimizer'
// and it's subclass implementations for the above four formats.
- applyOptimizer(response, imageFormat, image, config);
+ applyOptimizer(response, imageFormat, jpegImageParams, image, config);
} catch (IOException ioe) {
if (LOG.isLoggable(Level.WARNING)) {
LOG.logp(Level.WARNING, classname, "rewrite",
MessageKeys.IO_ERROR_REWRITING_IMG, new Object[]
{request.toString(),ioe.getMessage()});
@@ -433,7 +438,8 @@ public class BasicImageRewriter implemen
}
protected void applyOptimizer(HttpResponseBuilder response, ImageFormat
imageFormat,
- BufferedImage image, OptimizerConfig config) throws IOException {
+ JpegImageUtils.JpegImageParams jpegImageParams, BufferedImage image,
+ OptimizerConfig config) throws IOException {
if (imageFormat == ImageFormat.IMAGE_FORMAT_GIF) {
// Detecting the existence of the NETSCAPE2.0 extension by string
comparison
// is not exactly clean but is good enough to determine if a GIF is
animated
@@ -444,7 +450,7 @@ public class BasicImageRewriter implemen
} else if (imageFormat == ImageFormat.IMAGE_FORMAT_PNG) {
new PNGOptimizer(config, response).rewrite(image);
} else if (imageFormat == ImageFormat.IMAGE_FORMAT_JPEG) {
- new JPEGOptimizer(config, response).rewrite(image);
+ new JPEGOptimizer(config, response, jpegImageParams).rewrite(image);
} else if (imageFormat == ImageFormat.IMAGE_FORMAT_BMP) {
new BMPOptimizer(config, response).rewrite(image);
}
Modified:
shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/image/JPEGOptimizer.java
URL:
http://svn.apache.org/viewvc/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/image/JPEGOptimizer.java?rev=1066875&r1=1066874&r2=1066875&view=diff
==============================================================================
---
shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/image/JPEGOptimizer.java
(original)
+++
shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/image/JPEGOptimizer.java
Thu Feb 3 17:15:45 2011
@@ -57,6 +57,11 @@ public class JPEGOptimizer extends BaseO
super(config, response);
}
+ public JPEGOptimizer(OptimizerConfig config, HttpResponseBuilder response,
+ JpegImageUtils.JpegImageParams sourceImageParams) {
+ super(config, response, sourceImageParams);
+ }
+
@Override
protected void rewriteImpl(BufferedImage image) throws IOException {
int pngLength = Integer.MAX_VALUE;
@@ -64,7 +69,8 @@ public class JPEGOptimizer extends BaseO
// Create a new optimizer config and disable JPEG conversion
OptimizerConfig pngConfig = new
OptimizerConfig(config.getMaxInMemoryBytes(),
config.getMaxPaletteSize(), false, config.getJpegCompression(),
- config.getMinThresholdBytes(), config.getJpegHuffmanOptimization());
+ config.getMinThresholdBytes(), config.getJpegHuffmanOptimization(),
+ config.getJpegRetainSubsampling());
// Output the image as PNG
PNGOptimizer pngOptimizer = new PNGOptimizer(pngConfig, response);
Modified:
shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/image/OptimizerConfig.java
URL:
http://svn.apache.org/viewvc/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/image/OptimizerConfig.java?rev=1066875&r1=1066874&r2=1066875&view=diff
==============================================================================
---
shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/image/OptimizerConfig.java
(original)
+++
shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/image/OptimizerConfig.java
Thu Feb 3 17:15:45 2011
@@ -31,6 +31,7 @@ public class OptimizerConfig {
private final float jpegCompression;
private final int minThresholdBytes;
private final boolean jpegHuffmanOptimization;
+ private final boolean jpegRetainSubsampling;
@Inject
public OptimizerConfig(
@@ -39,7 +40,8 @@ public class OptimizerConfig {
@Named("shindig.image-rewrite.allow-jpeg-conversion") boolean
jpegConversionAllowed,
@Named("shindig.image-rewrite.jpeg-compression") float jpegCompression,
@Named("shindig.image-rewrite.min-threshold-bytes") int
minThresholdBytes,
- @Named("shindig.image-rewrite.jpeg-huffman-optimization") boolean
jpegHuffmanOptimization) {
+ @Named("shindig.image-rewrite.jpeg-huffman-optimization") boolean
jpegHuffmanOptimization,
+ @Named("shindig.image-rewrite.jpeg-retain-subsampling") boolean
jpegRetainSubsampling) {
this.maxInMemoryBytes = maxInMemoryBytes;
this.maxPaletteSize = maxPaletteSize;
this.jpegConversionAllowed = jpegConversionAllowed;
@@ -48,13 +50,14 @@ public class OptimizerConfig {
this.jpegCompression = Math.min(0.9f,Math.max(0.5f, jpegCompression));
this.minThresholdBytes = minThresholdBytes;
this.jpegHuffmanOptimization = jpegHuffmanOptimization;
+ this.jpegRetainSubsampling = jpegRetainSubsampling;
}
/**
* Defaults for usage in tests.
*/
public OptimizerConfig() {
- this(1024 * 1024, 256, true, 0.90f, 200, false);
+ this(1024 * 1024, 256, true, 0.90f, 200, false, false);
}
/**
@@ -102,4 +105,12 @@ public class OptimizerConfig {
public boolean getJpegHuffmanOptimization() {
return jpegHuffmanOptimization;
}
+
+ /**
+ * Indicate if we want to do retian original jpeg subsampling while encoding
the jpeg's.
+ */
+ public boolean getJpegRetainSubsampling() {
+ return jpegRetainSubsampling;
+ }
+
}
Modified:
shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/rewrite/image/JPEGOptimizerTest.java
URL:
http://svn.apache.org/viewvc/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/rewrite/image/JPEGOptimizerTest.java?rev=1066875&r1=1066874&r2=1066875&view=diff
==============================================================================
---
shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/rewrite/image/JPEGOptimizerTest.java
(original)
+++
shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/rewrite/image/JPEGOptimizerTest.java
Thu Feb 3 17:15:45 2011
@@ -48,6 +48,28 @@ public class JPEGOptimizerTest extends B
}
@Test
+ public void testRetainSubsmaplingDisabled() throws Exception {
+ HttpResponse resp =
+
createResponse("org/apache/shindig/gadgets/rewrite/image/testImage444.jpg",
"image/jpeg");
+ HttpResponse rewritten = rewrite(resp, getConfigWithRetainSampling(false,
0.70f));
+ JpegImageUtils.JpegImageParams params =
+ JpegImageUtils.getJpegImageData(rewritten.getResponse(), "");
+ assertTrue(rewritten.getContentLength() < resp.getContentLength());
+ assertEquals(JpegImageUtils.SamplingModes.YUV420,
params.getSamplingMode());
+ }
+
+ @Test
+ public void testRetainSubsmaplingEnabled() throws Exception {
+ HttpResponse resp =
+
createResponse("org/apache/shindig/gadgets/rewrite/image/testImage444.jpg",
"image/jpeg");
+ HttpResponse rewritten = rewrite(resp, getConfigWithRetainSampling(true,
0.70f));
+ JpegImageUtils.JpegImageParams params =
+ JpegImageUtils.getJpegImageData(rewritten.getResponse(), "");
+ assertTrue(rewritten.getContentLength() < resp.getContentLength());
+ assertEquals(JpegImageUtils.SamplingModes.YUV444,
params.getSamplingMode());
+ }
+
+ @Test
public void testLargeJPEG() throws Exception {
HttpResponse resp =
createResponse("org/apache/shindig/gadgets/rewrite/image/large.jpg",
"image/jpeg");
@@ -115,8 +137,9 @@ public class JPEGOptimizerTest extends B
HttpResponse rewrite(HttpResponse original, OptimizerConfig config)
throws IOException, ImageReadException {
HttpResponseBuilder builder = new HttpResponseBuilder(original);
- new JPEGOptimizer(config, builder).rewrite(
- JPEGOptimizer.readJpeg(original.getResponse()));
+ new JPEGOptimizer(config, builder,
+
JpegImageUtils.getJpegImageData(builder.getContentBytes(), ""))
+ .rewrite(JPEGOptimizer.readJpeg(original.getResponse()));
return builder.create();
}
@@ -125,6 +148,14 @@ public class JPEGOptimizerTest extends B
return new OptimizerConfig(defaultConfig.getMaxInMemoryBytes(),
defaultConfig.getMaxPaletteSize(), false,
defaultConfig.getJpegCompression(), defaultConfig.getMinThresholdBytes(),
- defaultConfig.getJpegHuffmanOptimization());
+ defaultConfig.getJpegHuffmanOptimization(),
defaultConfig.getJpegRetainSubsampling());
+ }
+
+ OptimizerConfig getConfigWithRetainSampling(boolean enabled, float quality) {
+ OptimizerConfig defaultConfig = new OptimizerConfig();
+ return new OptimizerConfig(defaultConfig.getMaxInMemoryBytes(),
+ defaultConfig.getMaxPaletteSize(),
defaultConfig.isJpegConversionAllowed(),
+ quality, defaultConfig.getMinThresholdBytes(),
defaultConfig.getJpegHuffmanOptimization(),
+ enabled);
}
}
\ No newline at end of file
Modified:
shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/rewrite/image/OptimizerConfigTest.java
URL:
http://svn.apache.org/viewvc/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/rewrite/image/OptimizerConfigTest.java?rev=1066875&r1=1066874&r2=1066875&view=diff
==============================================================================
---
shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/rewrite/image/OptimizerConfigTest.java
(original)
+++
shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/rewrite/image/OptimizerConfigTest.java
Thu Feb 3 17:15:45 2011
@@ -29,19 +29,19 @@ public class OptimizerConfigTest {
@Test
public void testForHighJpegCompression() {
- OptimizerConfig config = new OptimizerConfig(1024 * 1024, 256, true,
1.00f, 200, false);
+ OptimizerConfig config = new OptimizerConfig(1024 * 1024, 256, true,
1.00f, 200, false, false);
assertEquals(0.9f, config.getJpegCompression(), 0.0001);
}
@Test
public void testForLowJpegCompression() {
- OptimizerConfig config = new OptimizerConfig(1024 * 1024, 256, true,
0.10f, 200, false);
+ OptimizerConfig config = new OptimizerConfig(1024 * 1024, 256, true,
0.10f, 200, false, false);
assertEquals(0.5f, config.getJpegCompression() , 0.0001);
}
@Test
public void testForAcceptableJpegCompression() {
- OptimizerConfig config = new OptimizerConfig(1024 * 1024, 256, true,
0.85f, 200, false);
+ OptimizerConfig config = new OptimizerConfig(1024 * 1024, 256, true,
0.85f, 200, false, false);
assertEquals(0.85f, config.getJpegCompression(), 0.0001);
}
}