Index: test/java/org/apache/xmlgraphics/image/loader/impl/ImageLoaderPNGTestCase.java =================================================================== --- test/java/org/apache/xmlgraphics/image/loader/impl/ImageLoaderPNGTestCase.java (revision 0) +++ test/java/org/apache/xmlgraphics/image/loader/impl/ImageLoaderPNGTestCase.java (revision 0) @@ -0,0 +1,62 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.xmlgraphics.image.loader.impl; + +import java.io.IOException; + +import org.junit.Test; + +import org.apache.xmlgraphics.image.loader.Image; +import org.apache.xmlgraphics.image.loader.ImageContext; +import org.apache.xmlgraphics.image.loader.ImageException; +import org.apache.xmlgraphics.image.loader.ImageFlavor; +import org.apache.xmlgraphics.image.loader.ImageInfo; +import org.apache.xmlgraphics.image.loader.ImageSessionContext; +import org.apache.xmlgraphics.image.loader.MockImageContext; +import org.apache.xmlgraphics.image.loader.MockImageSessionContext; +import org.apache.xmlgraphics.util.MimeConstants; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class ImageLoaderPNGTestCase { + + private ImageLoaderPNG ilpng = new ImageLoaderPNG(); + + @Test + public void testGetUsagePenalty() { + assertEquals(1000, ilpng.getUsagePenalty()); + } + + @Test + public void testLoadImageImageInfoMapImageSessionContext() throws ImageException, IOException { + ImageContext context = MockImageContext.newSafeInstance(); + ImageSessionContext session = new MockImageSessionContext(context); + ImageInfo info = new ImageInfo("basn2c08.png", MimeConstants.MIME_PNG); + Image im = ilpng.loadImage(info, null, session); + assertTrue(im instanceof ImageRendered); + } + + @Test + public void testGetTargetFlavor() { + assertEquals(ImageFlavor.RENDERED_IMAGE, ilpng.getTargetFlavor()); + } + +} Index: test/java/org/apache/xmlgraphics/image/loader/impl/PNGFileTestCase.java =================================================================== --- test/java/org/apache/xmlgraphics/image/loader/impl/PNGFileTestCase.java (revision 0) +++ test/java/org/apache/xmlgraphics/image/loader/impl/PNGFileTestCase.java (revision 0) @@ -0,0 +1,119 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.xmlgraphics.image.loader.impl; + +import java.awt.image.ColorModel; +import java.awt.image.ComponentColorModel; +import java.awt.image.IndexColorModel; +import java.io.IOException; + +import org.junit.Test; + +import org.apache.xmlgraphics.image.loader.ImageContext; +import org.apache.xmlgraphics.image.loader.ImageException; +import org.apache.xmlgraphics.image.loader.ImageInfo; +import org.apache.xmlgraphics.image.loader.ImageSessionContext; +import org.apache.xmlgraphics.image.loader.MockImageContext; +import org.apache.xmlgraphics.image.loader.MockImageSessionContext; +import org.apache.xmlgraphics.util.MimeConstants; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +public class PNGFileTestCase implements PNGConstants { + + @Test + public void testColorTypeTwoPNG() throws ImageException, IOException { + testColorTypePNG("basn2c08.png", PNG_COLOR_RGB); + } + + @Test + public void testColorTypeZeroPNG() throws ImageException, IOException { + testColorTypePNG("basn0g08.png", PNG_COLOR_GRAY); + } + + @Test + public void testColorTypeSixPNG() throws ImageException, IOException { + testColorTypePNG("basn6a08.png", PNG_COLOR_RGB_ALPHA); + } + + @Test + public void testColorTypeThreePNG() throws ImageException, IOException { + testColorTypePNG("basn3p08.png", PNG_COLOR_PALETTE); + } + + @Test + public void testColorTypeFourPNG() throws ImageException, IOException { + testColorTypePNG("basn4a08.png", PNG_COLOR_GRAY_ALPHA); + } + + @Test + public void testTransparentPNG() throws ImageException, IOException { + testColorTypePNG("tbbn3p08.png", PNG_COLOR_PALETTE, true); + testColorTypePNG("tbrn2c08.png", PNG_COLOR_RGB, true); + } + + @Test + public void testCorruptPNG() { + ImageContext context = MockImageContext.newSafeInstance(); + ImageSessionContext session = new MockImageSessionContext(context); + ImageInfo info = new ImageInfo("corrupt-image.png", MimeConstants.MIME_PNG); + ImageLoaderRawPNG ilrpng = new ImageLoaderRawPNG(); + try { + ImageRawPNG irpng = (ImageRawPNG) ilrpng.loadImage(info, null, session); + fail("An exception should have been thrown above"); + } catch (Exception e) { + // do nothing; this was expected + } + } + + private void testColorTypePNG(String imageName, int colorType) throws ImageException, IOException { + testColorTypePNG(imageName, colorType, false); + } + + private void testColorTypePNG(String imageName, int colorType, boolean isTransparent) + throws ImageException, IOException { + ImageContext context = MockImageContext.newSafeInstance(); + ImageSessionContext session = new MockImageSessionContext(context); + ImageInfo info = new ImageInfo(imageName, MimeConstants.MIME_PNG); + ImageLoaderRawPNG ilrpng = new ImageLoaderRawPNG(); + ImageRawPNG irpng = (ImageRawPNG) ilrpng.loadImage(info, null, session); + ColorModel cm = irpng.getColorModel(); + if (colorType == PNG_COLOR_PALETTE) { + assertTrue(cm instanceof IndexColorModel); + } else { + assertTrue(cm instanceof ComponentColorModel); + int numComponents = 3; + if (colorType == PNG_COLOR_GRAY) { + numComponents = 1; + } else if (colorType == PNG_COLOR_GRAY_ALPHA) { + numComponents = 2; + } else if (colorType == PNG_COLOR_RGB_ALPHA) { + numComponents = 4; + } + assertEquals(numComponents, cm.getNumComponents()); + } + if (isTransparent) { + assertTrue(irpng.isTransparent()); + } + } + +} Index: test/java/org/apache/xmlgraphics/image/loader/impl/ImageLoaderRawPNGTestCase.java =================================================================== --- test/java/org/apache/xmlgraphics/image/loader/impl/ImageLoaderRawPNGTestCase.java (revision 0) +++ test/java/org/apache/xmlgraphics/image/loader/impl/ImageLoaderRawPNGTestCase.java (revision 0) @@ -0,0 +1,76 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.xmlgraphics.image.loader.impl; + +import java.io.IOException; + +import org.junit.Test; + +import org.apache.xmlgraphics.image.loader.Image; +import org.apache.xmlgraphics.image.loader.ImageContext; +import org.apache.xmlgraphics.image.loader.ImageException; +import org.apache.xmlgraphics.image.loader.ImageFlavor; +import org.apache.xmlgraphics.image.loader.ImageInfo; +import org.apache.xmlgraphics.image.loader.ImageSessionContext; +import org.apache.xmlgraphics.image.loader.MockImageContext; +import org.apache.xmlgraphics.image.loader.MockImageSessionContext; +import org.apache.xmlgraphics.util.MimeConstants; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +public class ImageLoaderRawPNGTestCase { + + private ImageLoaderRawPNG ilrpng = new ImageLoaderRawPNG(); + + @Test + public void testGetUsagePenalty() { + assertEquals(1000, ilrpng.getUsagePenalty()); + } + + @Test + public void testLoadImageBadMime() throws ImageException, IOException { + ImageContext context = MockImageContext.newSafeInstance(); + ImageSessionContext session = new MockImageSessionContext(context); + ImageInfo info = new ImageInfo("basn2c08.png", MimeConstants.MIME_JPEG); + try { + ImageRawPNG irpng = (ImageRawPNG) ilrpng.loadImage(info, null, session); + fail("An exception should have been thrown above"); + } catch (IllegalArgumentException e) { + // do nothing; this was expected + } + } + + @Test + public void testGetTargetFlavor() { + assertEquals(ImageFlavor.RAW_PNG, ilrpng.getTargetFlavor()); + } + + @Test + public void testLoadImageGoodMime() throws ImageException, IOException { + ImageContext context = MockImageContext.newSafeInstance(); + ImageSessionContext session = new MockImageSessionContext(context); + ImageInfo info = new ImageInfo("basn2c08.png", MimeConstants.MIME_PNG); + Image im = ilrpng.loadImage(info, null, session); + assertTrue(im instanceof ImageRawPNG); + } + +} Index: test/java/org/apache/xmlgraphics/image/loader/impl/ImageLoaderFactoryPNGTestCase.java =================================================================== --- test/java/org/apache/xmlgraphics/image/loader/impl/ImageLoaderFactoryPNGTestCase.java (revision 0) +++ test/java/org/apache/xmlgraphics/image/loader/impl/ImageLoaderFactoryPNGTestCase.java (revision 0) @@ -0,0 +1,64 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.xmlgraphics.image.loader.impl; + +import org.junit.Test; + +import org.apache.xmlgraphics.image.loader.ImageFlavor; +import org.apache.xmlgraphics.image.loader.spi.ImageLoader; +import org.apache.xmlgraphics.util.MimeConstants; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +public class ImageLoaderFactoryPNGTestCase { + + private ImageLoaderFactoryPNG ilfpng = new ImageLoaderFactoryPNG(); + + @Test + public void testGetSupportedMIMETypes() { + assertArrayEquals(new String[] {MimeConstants.MIME_PNG}, ilfpng.getSupportedMIMETypes()); + } + + @Test + public void testGetSupportedFlavors() { + assertArrayEquals(new ImageFlavor[] {ImageFlavor.RENDERED_IMAGE}, + ilfpng.getSupportedFlavors(MimeConstants.MIME_PNG)); + try { + ilfpng.getSupportedFlavors(MimeConstants.MIME_JPEG); + fail("An exception should have been thrown above...."); + } catch (IllegalArgumentException e) { + // do nothing; this is expected + } + } + + @Test + public void testNewImageLoader() { + ImageLoader il = ilfpng.newImageLoader(ImageFlavor.RENDERED_IMAGE); + assertTrue(il instanceof ImageLoaderPNG); + } + + @Test + public void testIsAvailable() { + assertTrue(ilfpng.isAvailable()); + } + +} Index: test/java/org/apache/xmlgraphics/image/loader/ImageLoaderTestCase.java =================================================================== --- test/java/org/apache/xmlgraphics/image/loader/ImageLoaderTestCase.java (revision 1349255) +++ test/java/org/apache/xmlgraphics/image/loader/ImageLoaderTestCase.java (working copy) @@ -34,6 +34,9 @@ import junit.framework.TestCase; import org.apache.commons.io.IOUtils; + +import org.apache.xmlgraphics.image.loader.impl.ImageLoaderPNG; +import org.apache.xmlgraphics.image.loader.impl.ImageLoaderRawPNG; import org.apache.xmlgraphics.image.loader.impl.ImageRawStream; import org.apache.xmlgraphics.image.loader.impl.ImageRendered; import org.apache.xmlgraphics.image.loader.spi.ImageImplRegistry; @@ -186,11 +189,15 @@ String mime, ImageFlavor rawFlavor) throws Exception { ImageLoaderFactory ilfs[] = ImageImplRegistry.getDefaultInstance() .getImageLoaderFactories(mime); - if (ilfs != null) + if (ilfs != null) { for (int i = 0; i < ilfs.length; i++) { ImageLoaderFactory ilf = ilfs[i]; try { final ImageLoader il = ilf.newImageLoader(rawFlavor); + if (il instanceof ImageLoaderRawPNG || il instanceof ImageLoaderPNG) { + // temporary measure until ImageLoaderRawPNG and ImageLoader PNG handle ICC profiles + continue; + } final ImageInfo im = new ImageInfo(uri, mime); final Image img = il.loadImage(im, isc); final ICC_Profile icc = img.getICCProfile(); @@ -203,8 +210,7 @@ // Ignore. This imageLoader does not support RAW } try { - final ImageLoader il = ilf - .newImageLoader(ImageFlavor.BUFFERED_IMAGE); + final ImageLoader il = ilf.newImageLoader(ImageFlavor.BUFFERED_IMAGE); final ImageInfo im = new ImageInfo(uri, mime); final Image img = il.loadImage(im, isc); final ICC_Profile icc = img.getICCProfile(); @@ -213,6 +219,7 @@ // Ignore. This imageLoader does not support Buffered. } } + } } public void testBrokenIccPng() throws Exception { Index: test/java/org/apache/xmlgraphics/java2d/ps/PSGraphics2DTestCase.java =================================================================== --- test/java/org/apache/xmlgraphics/java2d/ps/PSGraphics2DTestCase.java (revision 1349255) +++ test/java/org/apache/xmlgraphics/java2d/ps/PSGraphics2DTestCase.java (working copy) @@ -1,6 +1,5 @@ /* * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 Index: src/resources/META-INF/services/org.apache.xmlgraphics.image.loader.spi.ImageLoaderFactory =================================================================== --- src/resources/META-INF/services/org.apache.xmlgraphics.image.loader.spi.ImageLoaderFactory (revision 1349255) +++ src/resources/META-INF/services/org.apache.xmlgraphics.image.loader.spi.ImageLoaderFactory (working copy) @@ -2,4 +2,5 @@ org.apache.xmlgraphics.image.loader.impl.ImageLoaderFactoryRaw org.apache.xmlgraphics.image.loader.impl.ImageLoaderFactoryRawCCITTFax org.apache.xmlgraphics.image.loader.impl.ImageLoaderFactoryEPS -org.apache.xmlgraphics.image.loader.impl.ImageLoaderFactoryInternalTIFF \ No newline at end of file +org.apache.xmlgraphics.image.loader.impl.ImageLoaderFactoryInternalTIFF +org.apache.xmlgraphics.image.loader.impl.ImageLoaderFactoryPNG \ No newline at end of file Index: src/java/org/apache/xmlgraphics/ps/PSImageUtils.java =================================================================== --- src/java/org/apache/xmlgraphics/ps/PSImageUtils.java (revision 1349255) +++ src/java/org/apache/xmlgraphics/ps/PSImageUtils.java (working copy) @@ -143,25 +143,23 @@ /** * Writes a bitmap image to the PostScript stream. - * @param img the bitmap image as a byte array + * @param encoder the image encoder + * @param imgDim the dimensions of the image + * @param imgDescription the name of the image * @param targetRect the target rectangle to place the image in + * @param colorModel the color model of the image * @param gen the PostScript generator * @throws IOException In case of an I/O exception */ - private static void writeImage(RenderedImage img, - Rectangle2D targetRect, PSGenerator gen) throws IOException { - ImageEncoder encoder = ImageEncodingHelper.createRenderedImageEncoder(img); - String imgDescription = img.getClass().getName(); + public static void writeImage(ImageEncoder encoder, Dimension imgDim, String imgDescription, + Rectangle2D targetRect, ColorModel colorModel, PSGenerator gen) + throws IOException { gen.saveGraphicsState(); translateAndScale(gen, null, targetRect); - gen.commentln("%AXGBeginBitmap: " + imgDescription); - gen.writeln("{{"); - // Template: (RawData is used for the EOF signal only) - // gen.write("/RawData currentfile filter def"); - // gen.write("/Data RawData [...] def"); + String implicitFilter = encoder.getImplicitFilter(); if (implicitFilter != null) { gen.writeln("/RawData currentfile /ASCII85Decode filter def"); @@ -175,11 +173,15 @@ gen.writeln("/Data RawData /RunLengthDecode filter def"); } } + PSDictionary imageDict = new PSDictionary(); imageDict.put("/DataSource", "Data"); - writeImageCommand(img, imageDict, gen); - /* the following two lines could be enabled if something still goes wrong + populateImageDictionary(imgDim, colorModel, imageDict); + writeImageCommand(imageDict, colorModel, gen); + + /* + * the following two lines could be enabled if something still goes wrong * gen.write("Data closefile"); * gen.write("RawData flushfile"); */ @@ -189,43 +191,40 @@ compressAndWriteBitmap(encoder, gen); - gen.writeln(""); + gen.newLine(); gen.commentln("%AXGEndBitmap"); gen.restoreGraphicsState(); } - private static ColorModel populateImageDictionary( - ImageEncodingHelper helper, PSDictionary imageDict) { - RenderedImage img = helper.getImage(); - String w = Integer.toString(img.getWidth()); - String h = Integer.toString(img.getHeight()); + private static ColorModel populateImageDictionary(Dimension imgDim, ColorModel colorModel, + PSDictionary imageDict) { + String w = Integer.toString(imgDim.width); + String h = Integer.toString(imgDim.height); imageDict.put("/ImageType", "1"); imageDict.put("/Width", w); imageDict.put("/Height", h); - ColorModel cm = helper.getEncodedColorModel(); - boolean invertColors = false; - String decodeArray = getDecodeArray(cm.getNumComponents(), invertColors); - int bitsPerComp = cm.getComponentSize(0); + String decodeArray = getDecodeArray(colorModel.getNumColorComponents(), invertColors); + int bitsPerComp = colorModel.getComponentSize(0); // Setup scanning for left-to-right and top-to-bottom imageDict.put("/ImageMatrix", "[" + w + " 0 0 " + h + " 0 0]"); - if ((cm instanceof IndexColorModel)) { - IndexColorModel im = (IndexColorModel)cm; - int c = im.getMapSize(); + if ((colorModel instanceof IndexColorModel)) { + IndexColorModel indexColorModel = (IndexColorModel) colorModel; + int c = indexColorModel.getMapSize(); int hival = c - 1; if (hival > 4095) { throw new UnsupportedOperationException("hival must not go beyond 4095"); } - bitsPerComp = im.getPixelSize(); - int ceiling = ((int)Math.pow(2, bitsPerComp)) - 1; + bitsPerComp = indexColorModel.getPixelSize(); + int ceiling = ((int) Math.pow(2, bitsPerComp)) - 1; decodeArray = "[0 " + ceiling + "]"; } imageDict.put("/BitsPerComponent", Integer.toString(bitsPerComp)); imageDict.put("/Decode", decodeArray); - return cm; + return colorModel; } private static String getDecodeArray(int numComponents, boolean invertColors) { @@ -288,8 +287,9 @@ PSDictionary imageDict, PSGenerator gen) throws IOException { ImageEncodingHelper helper = new ImageEncodingHelper(img, true); ColorModel cm = helper.getEncodedColorModel(); - populateImageDictionary(helper, imageDict); + Dimension imgDim = new Dimension(img.getWidth(), img.getHeight()); + populateImageDictionary(imgDim, cm, imageDict); writeImageCommand(imageDict, cm, gen); } @@ -345,7 +345,13 @@ float x, float y, float w, float h, PSGenerator gen) throws IOException { Rectangle2D targetRect = new Rectangle2D.Double(x, y, w, h); - writeImage(img, targetRect, gen); + ImageEncoder encoder = ImageEncodingHelper.createRenderedImageEncoder(img); + Dimension imgDim = new Dimension(img.getWidth(), img.getHeight()); + String imgDescription = img.getClass().getName(); + ImageEncodingHelper helper = new ImageEncodingHelper(img); + ColorModel cm = helper.getEncodedColorModel(); + + writeImage(encoder, imgDim, imgDescription, targetRect, cm, gen); } /** Index: src/java/org/apache/xmlgraphics/image/loader/impl/ImageLoaderFactoryRaw.java =================================================================== --- src/java/org/apache/xmlgraphics/image/loader/impl/ImageLoaderFactoryRaw.java (revision 1349255) +++ src/java/org/apache/xmlgraphics/image/loader/impl/ImageLoaderFactoryRaw.java (working copy) @@ -81,6 +81,8 @@ public ImageLoader newImageLoader(ImageFlavor targetFlavor) { if (targetFlavor.equals(ImageFlavor.RAW_JPEG)) { return new ImageLoaderRawJPEG(); + } else if (targetFlavor.equals(ImageFlavor.RAW_PNG)) { + return new ImageLoaderRawPNG(); } else { return new ImageLoaderRaw(targetFlavor); } Index: src/java/org/apache/xmlgraphics/image/loader/impl/ImageLoaderRawPNG.java =================================================================== --- src/java/org/apache/xmlgraphics/image/loader/impl/ImageLoaderRawPNG.java (revision 0) +++ src/java/org/apache/xmlgraphics/image/loader/impl/ImageLoaderRawPNG.java (revision 0) @@ -0,0 +1,85 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +// Original author: Matthias Reichenbacher + +package org.apache.xmlgraphics.image.loader.impl; + +import java.io.IOException; +import java.util.Map; + +import javax.imageio.stream.ImageInputStream; +import javax.xml.transform.Source; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.apache.xmlgraphics.image.codec.util.ImageInputStreamSeekableStreamAdapter; +import org.apache.xmlgraphics.image.codec.util.SeekableStream; +import org.apache.xmlgraphics.image.loader.Image; +import org.apache.xmlgraphics.image.loader.ImageException; +import org.apache.xmlgraphics.image.loader.ImageFlavor; +import org.apache.xmlgraphics.image.loader.ImageInfo; +import org.apache.xmlgraphics.image.loader.ImageSessionContext; +import org.apache.xmlgraphics.image.loader.util.ImageUtil; +import org.apache.xmlgraphics.util.MimeConstants; + +public class ImageLoaderRawPNG extends AbstractImageLoader { + + /** logger */ + protected static Log log = LogFactory.getLog(ImageLoaderRawPNG.class); + + /** + * Main constructor. + */ + public ImageLoaderRawPNG() { + } + + /** {@inheritDoc} */ + public ImageFlavor getTargetFlavor() { + return ImageFlavor.RAW_PNG; + } + + /** {@inheritDoc} */ + public Image loadImage(ImageInfo info, Map hints, ImageSessionContext session) throws ImageException, + IOException { + if (!MimeConstants.MIME_PNG.equals(info.getMimeType())) { + throw new IllegalArgumentException("ImageInfo must be from a image with MIME type: " + + MimeConstants.MIME_PNG); + } + + Source src = session.needSource(info.getOriginalURI()); + ImageInputStream in = ImageUtil.needImageInputStream(src); + // Remove streams as we do things with them at some later time. + ImageUtil.removeStreams(src); + SeekableStream seekStream = new ImageInputStreamSeekableStreamAdapter(in); + PNGFile im = new PNGFile(seekStream); + ImageRawPNG irpng = im.getImageRawPNG(info); + return irpng; + } + + /** {@inheritDoc} */ + public int getUsagePenalty() { + // since this image loader does not handle all kinds of PNG images then we add some penalty to it + // so that it is not chosen by default; instead, users need to give it a negative penalty in + // fop.xconf so that it is used + return 1000; + } + +} Index: src/java/org/apache/xmlgraphics/image/loader/impl/PNGConstants.java =================================================================== --- src/java/org/apache/xmlgraphics/image/loader/impl/PNGConstants.java (revision 0) +++ src/java/org/apache/xmlgraphics/image/loader/impl/PNGConstants.java (revision 0) @@ -0,0 +1,47 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.xmlgraphics.image.loader.impl; + +public interface PNGConstants { + + /* + * First 8 bytes of any PNG file. + */ + long PNG_SIGNATURE = 0x89504e470d0a1a0aL; + + /* + * Color types. + */ + int PNG_COLOR_GRAY = 0; + int PNG_COLOR_RGB = 2; + int PNG_COLOR_PALETTE = 3; + int PNG_COLOR_GRAY_ALPHA = 4; + int PNG_COLOR_RGB_ALPHA = 6; + + /* + * Filter types. + */ + int PNG_FILTER_NONE = 0; + int PNG_FILTER_SUB = 1; + int PNG_FILTER_UP = 2; + int PNG_FILTER_AVERAGE = 3; + int PNG_FILTER_PAETH = 4; + +} Index: src/java/org/apache/xmlgraphics/image/loader/impl/ImageLoaderFactoryPNG.java =================================================================== --- src/java/org/apache/xmlgraphics/image/loader/impl/ImageLoaderFactoryPNG.java (revision 0) +++ src/java/org/apache/xmlgraphics/image/loader/impl/ImageLoaderFactoryPNG.java (revision 0) @@ -0,0 +1,59 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.xmlgraphics.image.loader.impl; + +import org.apache.xmlgraphics.image.loader.ImageFlavor; +import org.apache.xmlgraphics.image.loader.spi.ImageLoader; +import org.apache.xmlgraphics.util.MimeConstants; + +public class ImageLoaderFactoryPNG extends AbstractImageLoaderFactory { + + private static final String[] MIMES = new String[] {MimeConstants.MIME_PNG}; + + private static final ImageFlavor[] FLAVORS = new ImageFlavor[] {ImageFlavor.RENDERED_IMAGE}; + + public ImageLoaderFactoryPNG() { + // + } + + /** {@inheritDoc} */ + public String[] getSupportedMIMETypes() { + return MIMES; + } + + /** {@inheritDoc} */ + public ImageFlavor[] getSupportedFlavors(String mime) { + if (MimeConstants.MIME_PNG.equals(mime)) { + return FLAVORS; + } + throw new IllegalArgumentException("Unsupported MIME type: " + mime); + } + + /** {@inheritDoc} */ + public ImageLoader newImageLoader(ImageFlavor targetFlavor) { + return new ImageLoaderPNG(); + } + + /** {@inheritDoc} */ + public boolean isAvailable() { + return true; + } + +} Index: src/java/org/apache/xmlgraphics/image/loader/impl/ImageLoaderPNG.java =================================================================== --- src/java/org/apache/xmlgraphics/image/loader/impl/ImageLoaderPNG.java (revision 0) +++ src/java/org/apache/xmlgraphics/image/loader/impl/ImageLoaderPNG.java (revision 0) @@ -0,0 +1,75 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.xmlgraphics.image.loader.impl; + +import java.awt.image.RenderedImage; +import java.io.IOException; +import java.util.Map; + +import javax.imageio.stream.ImageInputStream; +import javax.xml.transform.Source; + +import org.apache.xmlgraphics.image.codec.png.PNGDecodeParam; +import org.apache.xmlgraphics.image.codec.png.PNGImageDecoder; +import org.apache.xmlgraphics.image.codec.util.ImageInputStreamSeekableStreamAdapter; +import org.apache.xmlgraphics.image.codec.util.SeekableStream; +import org.apache.xmlgraphics.image.loader.Image; +import org.apache.xmlgraphics.image.loader.ImageException; +import org.apache.xmlgraphics.image.loader.ImageFlavor; +import org.apache.xmlgraphics.image.loader.ImageInfo; +import org.apache.xmlgraphics.image.loader.ImageSessionContext; +import org.apache.xmlgraphics.image.loader.util.ImageUtil; + +public class ImageLoaderPNG extends AbstractImageLoader { + + public ImageLoaderPNG() { + // + } + + /** {@inheritDoc} */ + public Image loadImage(ImageInfo info, Map hints, ImageSessionContext session) throws ImageException, + IOException { + + Source src = session.needSource(info.getOriginalURI()); + ImageInputStream imgStream = ImageUtil.needImageInputStream(src); + + SeekableStream seekStream = new ImageInputStreamSeekableStreamAdapter(imgStream); + + PNGImageDecoder decoder = new PNGImageDecoder(seekStream, new PNGDecodeParam()); + RenderedImage image = decoder.decodeAsRenderedImage(); + + // need transparency here? + return new ImageRendered(info, image, null); + } + + /** {@inheritDoc} */ + public ImageFlavor getTargetFlavor() { + return ImageFlavor.RENDERED_IMAGE; + } + + /** {@inheritDoc} */ + public int getUsagePenalty() { + // since this image loader does not provide any benefits over the default sun.imageio one we add + // some penalty to it so that it is not chosen by default; instead users need to give it a negative + // penalty in fop.xconf so that it is used; this image loader is mostly for testing purposes for now. + return 1000; + } + +} Index: src/java/org/apache/xmlgraphics/image/loader/impl/ImageRawPNG.java =================================================================== --- src/java/org/apache/xmlgraphics/image/loader/impl/ImageRawPNG.java (revision 0) +++ src/java/org/apache/xmlgraphics/image/loader/impl/ImageRawPNG.java (revision 0) @@ -0,0 +1,144 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +// Original author: Matthias Reichenbacher + +package org.apache.xmlgraphics.image.loader.impl; + +import java.awt.Color; +import java.awt.color.ColorSpace; +import java.awt.color.ICC_Profile; +import java.awt.image.ColorModel; +import java.io.InputStream; + +import org.apache.xmlgraphics.image.loader.ImageFlavor; +import org.apache.xmlgraphics.image.loader.ImageInfo; + +public class ImageRawPNG extends ImageRawStream { + + private ColorModel cm; + private ICC_Profile iccProfile; + private int bitDepth; + private boolean isTransparent; + private int grayTransparentAlpha; + private int redTransparentAlpha; + private int greenTransparentAlpha; + private int blueTransparentAlpha; + + /** + * Main constructor. + * @param info the image info object + * @param in the ImageInputStream with the raw content + * @param colorSpace the color space + * @param iccProfile an ICC color profile or null if no profile is associated + */ + public ImageRawPNG(ImageInfo info, InputStream in, ColorModel cm, int bitDepth, ICC_Profile iccProfile) { + super(info, ImageFlavor.RAW_PNG, in); + this.iccProfile = iccProfile; + this.cm = cm; + this.bitDepth = bitDepth; + } + + /** + * The bit depth of each color channel. + * @return the bit depth of one channel (same for all) + */ + public int getBitDepth() { + return bitDepth; + } + + /** + * Returns the ICC color profile if one is associated with the PNG image. + * @return the ICC color profile or null if there's no profile + */ + public ICC_Profile getICCProfile() { + return this.iccProfile; + } + + /** + * Returns the image's color model. + * @return the color model + */ + public ColorModel getColorModel() { + return this.cm; + } + + /** + * Returns the image's color space. + * @return the color space + */ + public ColorSpace getColorSpace() { + return this.cm.getColorSpace(); + } + + /** + * Sets the gray transparent pixel value. + * @param gray the transparent pixel gray value (0...255) + */ + protected void setGrayTransparentAlpha(int gray) { + this.isTransparent = true; + this.grayTransparentAlpha = gray; + } + + /** + * Sets the RGB transparent pixel values. + * @param red the transparent pixel red value (0...255) + * @param green the transparent pixel green value (0...255) + * @param blue the transparent pixel blue value (0...255) + */ + protected void setRGBTransparentAlpha(int red, int green, int blue) { + this.isTransparent = true; + this.redTransparentAlpha = red; + this.greenTransparentAlpha = green; + this.blueTransparentAlpha = blue; + } + + /** + * Used to flag image as transparent when the image is of pallete type. + */ + protected void setTransparent() { + this.isTransparent = true; + } + + /** + * Whether the image is transparent (meaning there is a transparent pixel) + * @return true if transparent pixel exists + */ + public boolean isTransparent() { + return this.isTransparent; + } + + /** + * The color of the transparent pixel. + * @return the color of the transparent pixel. + */ + public Color getTransparentColor() { + Color color = null; + if (!this.isTransparent) { + return color; + } + if (cm.getNumColorComponents() == 3) { + color = new Color(this.redTransparentAlpha, this.greenTransparentAlpha, this.blueTransparentAlpha); + } else { + color = new Color(this.grayTransparentAlpha, 0, 0); + } + return color; + } + +} Index: src/java/org/apache/xmlgraphics/image/loader/impl/PNGFile.java =================================================================== --- src/java/org/apache/xmlgraphics/image/loader/impl/PNGFile.java (revision 0) +++ src/java/org/apache/xmlgraphics/image/loader/impl/PNGFile.java (revision 0) @@ -0,0 +1,236 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.xmlgraphics.image.loader.impl; + +import java.awt.color.ColorSpace; +import java.awt.color.ICC_Profile; +import java.awt.image.ColorModel; +import java.awt.image.ComponentColorModel; +import java.awt.image.DataBuffer; +import java.awt.image.IndexColorModel; +import java.io.BufferedInputStream; +import java.io.ByteArrayInputStream; +import java.io.DataInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.SequenceInputStream; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.apache.xmlgraphics.image.codec.png.PNGChunk; +import org.apache.xmlgraphics.image.codec.util.PropertyUtil; +import org.apache.xmlgraphics.image.loader.ImageException; +import org.apache.xmlgraphics.image.loader.ImageInfo; + +/** + * Provides methods useful for processing PNG files. + */ +class PNGFile implements PNGConstants { + + private ColorModel colorModel; + private ICC_Profile iccProfile; + private int bitDepth; + private int colorType; + private boolean isTransparent; + private int grayTransparentAlpha; + private int redTransparentAlpha; + private int greenTransparentAlpha; + private int blueTransparentAlpha; + private List streamVec = new ArrayList(); + private int paletteEntries; + private byte[] redPalette; + private byte[] greenPalette; + private byte[] bluePalette; + private byte[] alphaPalette; + private boolean hasPalette; + private boolean hasAlphaPalette = false; + + public PNGFile(InputStream stream) throws IOException, ImageException { + if (!stream.markSupported()) { + stream = new BufferedInputStream(stream); + } + DataInputStream distream = new DataInputStream(stream); + long magic = distream.readLong(); + if (magic != PNG_SIGNATURE) { + String msg = PropertyUtil.getString("PNGImageDecoder0"); + throw new ImageException(msg); + } + // only some chunks are worth parsing in the current implementation + do { + try { + PNGChunk chunk; + String chunkType = PNGChunk.getChunkType(distream); + if (chunkType.equals(PNGChunk.ChunkType.IHDR.name())) { + chunk = PNGChunk.readChunk(distream); + parse_IHDR_chunk(chunk); + } else if (chunkType.equals(PNGChunk.ChunkType.PLTE.name())) { + chunk = PNGChunk.readChunk(distream); + parse_PLTE_chunk(chunk); + } else if (chunkType.equals(PNGChunk.ChunkType.IDAT.name())) { + chunk = PNGChunk.readChunk(distream); + streamVec.add(new ByteArrayInputStream(chunk.getData())); + } else if (chunkType.equals(PNGChunk.ChunkType.IEND.name())) { + // chunk = PNGChunk.readChunk(distream); + PNGChunk.skipChunk(distream); + break; // fall through to the bottom + } else if (chunkType.equals(PNGChunk.ChunkType.tRNS.name())) { + chunk = PNGChunk.readChunk(distream); + parse_tRNS_chunk(chunk); + } else { + // chunk = PNGChunk.readChunk(distream); + PNGChunk.skipChunk(distream); + } + } catch (Exception e) { + e.printStackTrace(); + String msg = PropertyUtil.getString("PNGImageDecoder2"); + throw new RuntimeException(msg); + } + } while (true); + } + + public ImageRawPNG getImageRawPNG(ImageInfo info) throws ImageException { + InputStream seqStream = new SequenceInputStream(Collections.enumeration(streamVec)); + switch (colorType) { + case PNG_COLOR_GRAY: + if (hasPalette) { + throw new ImageException("Corrupt PNG: color palette is not allowed!"); + } + colorModel = new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_GRAY), false, false, + ColorModel.OPAQUE, DataBuffer.TYPE_BYTE); + break; + case PNG_COLOR_RGB: + // actually a check of the sRGB chunk would be necessary to confirm if it's really sRGB + colorModel = new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_sRGB), false, false, + ColorModel.OPAQUE, DataBuffer.TYPE_BYTE); + break; + case PNG_COLOR_PALETTE: + if (hasAlphaPalette) { + colorModel = new IndexColorModel(bitDepth, paletteEntries, redPalette, greenPalette, + bluePalette, alphaPalette); + } else { + colorModel = new IndexColorModel(bitDepth, paletteEntries, redPalette, greenPalette, + bluePalette); + } + break; + case PNG_COLOR_GRAY_ALPHA: + if (hasPalette) { + throw new ImageException("Corrupt PNG: color palette is not allowed!"); + } + colorModel = new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_GRAY), true, false, + ColorModel.TRANSLUCENT, DataBuffer.TYPE_BYTE); + break; + case PNG_COLOR_RGB_ALPHA: + // actually a check of the sRGB chunk would be necessary to confirm if it's really sRGB + colorModel = new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_sRGB), true, false, + ColorModel.TRANSLUCENT, DataBuffer.TYPE_BYTE); + break; + default: + throw new ImageException("Unsupported color type: " + colorType); + } + // the iccProfile is still null for now + ImageRawPNG rawImage = new ImageRawPNG(info, seqStream, colorModel, bitDepth, iccProfile); + if (isTransparent) { + if (colorType == PNG_COLOR_GRAY) { + rawImage.setGrayTransparentAlpha(grayTransparentAlpha); + } else if (colorType == PNG_COLOR_RGB) { + rawImage.setRGBTransparentAlpha(redTransparentAlpha, greenTransparentAlpha, + blueTransparentAlpha); + } else if (colorType == PNG_COLOR_PALETTE) { + rawImage.setTransparent(); + } else { + // + } + } + return rawImage; + } + + private void parse_IHDR_chunk(PNGChunk chunk) { + int width = chunk.getInt4(0); + int height = chunk.getInt4(4); + bitDepth = chunk.getInt1(8); + if (bitDepth != 8) { + // this is a limitation of the current implementation + throw new RuntimeException("Unsupported bit depth: " + bitDepth); + } + colorType = chunk.getInt1(9); + int compressionMethod = chunk.getInt1(10); + if (compressionMethod != 0) { + throw new RuntimeException("Unsupported PNG compression method: " + compressionMethod); + } + int filterMethod = chunk.getInt1(11); + if (filterMethod != 0) { + throw new RuntimeException("Unsupported PNG filter method: " + filterMethod); + } + int interlaceMethod = chunk.getInt1(12); + if (interlaceMethod != 0) { + // this is a limitation of the current implementation + throw new RuntimeException("Unsupported PNG interlace method: " + interlaceMethod); + } + } + + private void parse_PLTE_chunk(PNGChunk chunk) { + paletteEntries = chunk.getLength() / 3; + redPalette = new byte[paletteEntries]; + greenPalette = new byte[paletteEntries]; + bluePalette = new byte[paletteEntries]; + hasPalette = true; + + int pltIndex = 0; + for (int i = 0; i < paletteEntries; i++) { + redPalette[i] = chunk.getByte(pltIndex++); + greenPalette[i] = chunk.getByte(pltIndex++); + bluePalette[i] = chunk.getByte(pltIndex++); + } + } + + private void parse_tRNS_chunk(PNGChunk chunk) { + if (colorType == PNG_COLOR_PALETTE) { + int entries = chunk.getLength(); + if (entries > paletteEntries) { + // Error -- mustn't have more alpha than RGB palette entries + String msg = PropertyUtil.getString("PNGImageDecoder14"); + throw new RuntimeException(msg); + } + // Load beginning of palette from the chunk + alphaPalette = new byte[paletteEntries]; + for (int i = 0; i < entries; i++) { + alphaPalette[i] = chunk.getByte(i); + } + // Fill rest of palette with 255 + for (int i = entries; i < paletteEntries; i++) { + alphaPalette[i] = (byte) 255; + } + hasAlphaPalette = true; + } else if (colorType == PNG_COLOR_GRAY) { + grayTransparentAlpha = chunk.getInt2(0); + } else if (colorType == PNG_COLOR_RGB) { + redTransparentAlpha = chunk.getInt2(0); + greenTransparentAlpha = chunk.getInt2(2); + blueTransparentAlpha = chunk.getInt2(4); + } else if (colorType == PNG_COLOR_GRAY_ALPHA || colorType == PNG_COLOR_RGB_ALPHA) { + // Error -- GA or RGBA image can't have a tRNS chunk. + String msg = PropertyUtil.getString("PNGImageDecoder15"); + throw new RuntimeException(msg); + } + isTransparent = true; + } + +} Index: src/java/org/apache/xmlgraphics/image/codec/png/PNGImageDecoder.java =================================================================== --- src/java/org/apache/xmlgraphics/image/codec/png/PNGImageDecoder.java (revision 1349255) +++ src/java/org/apache/xmlgraphics/image/codec/png/PNGImageDecoder.java (working copy) @@ -51,6 +51,7 @@ import org.apache.xmlgraphics.image.codec.util.ImageDecoderImpl; import org.apache.xmlgraphics.image.codec.util.PropertyUtil; import org.apache.xmlgraphics.image.codec.util.SimpleRenderedImage; +import org.apache.xmlgraphics.image.loader.impl.PNGConstants; // CSOFF: ConstantName // CSOFF: InnerAssignment @@ -80,101 +81,19 @@ } } -class PNGChunk { - int length; - int type; - byte[] data; - int crc; - - final String typeString; - - PNGChunk(int length, int type, byte[] data, int crc) { - this.length = length; - this.type = type; - this.data = data; - this.crc = crc; - - typeString = "" - + (char)((type >>> 24) & 0xff) - + (char)((type >>> 16) & 0xff) - + (char)((type >>> 8) & 0xff) - + (char)((type ) & 0xff); - } - - public int getLength() { - return length; - } - - public int getType() { - return type; - } - - public String getTypeString() { - return typeString; - } - - public byte[] getData() { - return data; - } - - public byte getByte(int offset) { - return data[offset]; - } - - public int getInt1(int offset) { - return data[offset] & 0xff; - } - - public int getInt2(int offset) { - return ((data[offset] & 0xff) << 8) | - (data[offset + 1] & 0xff); - } - - public int getInt4(int offset) { - return ((data[offset] & 0xff) << 24) | - ((data[offset + 1] & 0xff) << 16) | - ((data[offset + 2] & 0xff) << 8) | - (data[offset + 3] & 0xff); - } - - public String getString4(int offset) { - return "" - + (char)data[offset] - + (char)data[offset + 1] - + (char)data[offset + 2] - + (char)data[offset + 3]; - } - - public boolean isType(String typeName) { - return typeString.equals(typeName); - } -} - /** * TO DO: * * zTXt chunks * */ -class PNGImage extends SimpleRenderedImage { - - public static final int PNG_COLOR_GRAY = 0; - public static final int PNG_COLOR_RGB = 2; - public static final int PNG_COLOR_PALETTE = 3; - public static final int PNG_COLOR_GRAY_ALPHA = 4; - public static final int PNG_COLOR_RGB_ALPHA = 6; +class PNGImage extends SimpleRenderedImage implements PNGConstants { private static final String[] colorTypeNames = { "Grayscale", "Error", "Truecolor", "Index", "Grayscale with alpha", "Error", "Truecolor with alpha" }; - public static final int PNG_FILTER_NONE = 0; - public static final int PNG_FILTER_SUB = 1; - public static final int PNG_FILTER_UP = 2; - public static final int PNG_FILTER_AVERAGE = 3; - public static final int PNG_FILTER_PAETH = 4; - private int[][] bandOffsets = { null, { 0 }, // G @@ -304,7 +223,7 @@ private static final int POST_ADD_GRAY_TRANS_EXP = POST_ADD_GRAY_TRANS | POST_EXP_MASK; - private List streamVec = new ArrayList(); + private List streamVec = new ArrayList(); private DataInputStream dataStream; private int bytesPerPixel; // number of bytes per input pixel @@ -399,7 +318,7 @@ try { long magic = distream.readLong(); - if (magic != 0x89504e470d0a1a0aL) { + if (magic != PNG_SIGNATURE) { String msg = PropertyUtil.getString("PNGImageDecoder0"); throw new RuntimeException(msg); } @@ -413,58 +332,58 @@ try { PNGChunk chunk; - String chunkType = getChunkType(distream); - if (chunkType.equals("IHDR")) { - chunk = readChunk(distream); + String chunkType = PNGChunk.getChunkType(distream); + if (chunkType.equals(PNGChunk.ChunkType.IHDR.name())) { + chunk = PNGChunk.readChunk(distream); parse_IHDR_chunk(chunk); - } else if (chunkType.equals("PLTE")) { - chunk = readChunk(distream); + } else if (chunkType.equals(PNGChunk.ChunkType.PLTE.name())) { + chunk = PNGChunk.readChunk(distream); parse_PLTE_chunk(chunk); - } else if (chunkType.equals("IDAT")) { - chunk = readChunk(distream); + } else if (chunkType.equals(PNGChunk.ChunkType.IDAT.name())) { + chunk = PNGChunk.readChunk(distream); streamVec.add(new ByteArrayInputStream(chunk.getData())); - } else if (chunkType.equals("IEND")) { - chunk = readChunk(distream); + } else if (chunkType.equals(PNGChunk.ChunkType.IEND.name())) { + chunk = PNGChunk.readChunk(distream); parse_IEND_chunk(chunk); break; // fall through to the bottom - } else if (chunkType.equals("bKGD")) { - chunk = readChunk(distream); + } else if (chunkType.equals(PNGChunk.ChunkType.bKGD.name())) { + chunk = PNGChunk.readChunk(distream); parse_bKGD_chunk(chunk); - } else if (chunkType.equals("cHRM")) { - chunk = readChunk(distream); + } else if (chunkType.equals(PNGChunk.ChunkType.cHRM.name())) { + chunk = PNGChunk.readChunk(distream); parse_cHRM_chunk(chunk); - } else if (chunkType.equals("gAMA")) { - chunk = readChunk(distream); + } else if (chunkType.equals(PNGChunk.ChunkType.gAMA.name())) { + chunk = PNGChunk.readChunk(distream); parse_gAMA_chunk(chunk); - } else if (chunkType.equals("hIST")) { - chunk = readChunk(distream); + } else if (chunkType.equals(PNGChunk.ChunkType.hIST.name())) { + chunk = PNGChunk.readChunk(distream); parse_hIST_chunk(chunk); - } else if (chunkType.equals("iCCP")) { - chunk = readChunk(distream); + } else if (chunkType.equals(PNGChunk.ChunkType.iCCP.name())) { + chunk = PNGChunk.readChunk(distream); parse_iCCP_chunk(chunk); - } else if (chunkType.equals("pHYs")) { - chunk = readChunk(distream); + } else if (chunkType.equals(PNGChunk.ChunkType.pHYs.name())) { + chunk = PNGChunk.readChunk(distream); parse_pHYs_chunk(chunk); - } else if (chunkType.equals("sBIT")) { - chunk = readChunk(distream); + } else if (chunkType.equals(PNGChunk.ChunkType.sBIT.name())) { + chunk = PNGChunk.readChunk(distream); parse_sBIT_chunk(chunk); - } else if (chunkType.equals("sRGB")) { - chunk = readChunk(distream); + } else if (chunkType.equals(PNGChunk.ChunkType.sRGB.name())) { + chunk = PNGChunk.readChunk(distream); parse_sRGB_chunk(chunk); - } else if (chunkType.equals("tEXt")) { - chunk = readChunk(distream); + } else if (chunkType.equals(PNGChunk.ChunkType.tEXt.name())) { + chunk = PNGChunk.readChunk(distream); parse_tEXt_chunk(chunk); - } else if (chunkType.equals("tIME")) { - chunk = readChunk(distream); + } else if (chunkType.equals(PNGChunk.ChunkType.tIME.name())) { + chunk = PNGChunk.readChunk(distream); parse_tIME_chunk(chunk); - } else if (chunkType.equals("tRNS")) { - chunk = readChunk(distream); + } else if (chunkType.equals(PNGChunk.ChunkType.tRNS.name())) { + chunk = PNGChunk.readChunk(distream); parse_tRNS_chunk(chunk); - } else if (chunkType.equals("zTXt")) { - chunk = readChunk(distream); + } else if (chunkType.equals(PNGChunk.ChunkType.zTXt.name())) { + chunk = PNGChunk.readChunk(distream); parse_zTXt_chunk(chunk); } else { - chunk = readChunk(distream); + chunk = PNGChunk.readChunk(distream); // Output the chunk data in raw form String type = chunk.getTypeString(); @@ -498,40 +417,6 @@ } } - private static String getChunkType(DataInputStream distream) { - try { - distream.mark(8); - /* int length = */ distream.readInt(); - int type = distream.readInt(); - distream.reset(); - - String typeString = ""; // todo simplify this - typeString += (char)(type >> 24); - typeString += (char)((type >> 16) & 0xff); - typeString += (char)((type >> 8) & 0xff); - typeString += (char)(type & 0xff); - return typeString; - } catch (Exception e) { - e.printStackTrace(); - return null; - } - } - - private static PNGChunk readChunk(DataInputStream distream) { - try { - int length = distream.readInt(); - int type = distream.readInt(); - byte[] data = new byte[length]; - distream.readFully(data); - int crc = distream.readInt(); - - return new PNGChunk(length, type, data, crc); - } catch (Exception e) { - e.printStackTrace(); - return null; - } - } - private void parse_IHDR_chunk(PNGChunk chunk) { tileWidth = width = chunk.getInt4(0); tileHeight = height = chunk.getInt4(4); Index: src/java/org/apache/xmlgraphics/image/codec/png/PNGChunk.java =================================================================== --- src/java/org/apache/xmlgraphics/image/codec/png/PNGChunk.java (revision 0) +++ src/java/org/apache/xmlgraphics/image/codec/png/PNGChunk.java (revision 0) @@ -0,0 +1,184 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.xmlgraphics.image.codec.png; + +import java.io.DataInputStream; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +public class PNGChunk { + int length; + int type; + byte[] data; + int crc; + + String typeString; + + /** logger */ + protected static Log log = LogFactory.getLog(PNGChunk.class); + + /** + * See http://en.wikipedia.org/wiki/Portable_Network_Graphics for a light explanation; + * See http://www.libpng.org/pub/png/spec/1.2/PNG-Chunks.html for the spec. + */ + public enum ChunkType { + IHDR, // IHDR must be the first chunk + PLTE, // PLTE contains the palette + IDAT, // IDAT contains the image, which may be split among multiple IDAT chunks + IEND, // IEND marks the image end + bKGD, // bKGD gives the default background color + cHRM, // cHRM gives the chromaticity coordinates + gAMA, // gAMA specifies gamma + hIST, // hIST can store the histogram + iCCP, // iCCP is an ICC color profile + iTXt, // iTXt contains UTF-8 text + pHYs, // pHYs holds the intended pixel size + sBIT, // sBIT (significant bits) indicates the color-accuracy + sPLT, // sPLT suggests a palette to use + sRGB, // sRGB indicates that the standard sRGB color space is used + sTER, // sTER stereo-image indicator chunk for stereoscopic images + tEXt, // tEXt can store text that can be represented in ISO/IEC 8859-1 + tIME, // tIME stores the time that the image was last changed + tRNS, // tRNS contains transparency information + zTXt; // zTXt contains compressed text with the same limits as tEXt + } + + public PNGChunk(int length, int type, byte[] data, int crc) { + this.length = length; + this.type = type; + this.data = data; + this.crc = crc; + this.typeString = typeIntToString(this.type); + } + + public int getLength() { + return length; + } + + public int getType() { + return type; + } + + public String getTypeString() { + return typeString; + } + + public byte[] getData() { + return data; + } + + public byte getByte(int offset) { + return data[offset]; + } + + public int getInt1(int offset) { + return data[offset] & 0xff; + } + + public int getInt2(int offset) { + return ((data[offset] & 0xff) << 8) | (data[offset + 1] & 0xff); + } + + public int getInt4(int offset) { + return ((data[offset] & 0xff) << 24) | ((data[offset + 1] & 0xff) << 16) + | ((data[offset + 2] & 0xff) << 8) | (data[offset + 3] & 0xff); + } + + public String getString4(int offset) { + return "" + (char) data[offset] + (char) data[offset + 1] + (char) data[offset + 2] + + (char) data[offset + 3]; + } + + public boolean isType(String typeName) { + return typeString.equals(typeName); + } + + /** + * Reads the next chunk from the input stream. + * @param distream the input stream + * @return the chunk + */ + public static PNGChunk readChunk(DataInputStream distream) { + try { + int length = distream.readInt(); + int type = distream.readInt(); + byte[] data = new byte[length]; + distream.readFully(data); + int crc = distream.readInt(); + + return new PNGChunk(length, type, data, crc); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + + /** + * Returns the PNG chunk type, a four letter case sensitive ASCII type/name. + * @param distream the input stream + * @return a four letter case sensitive ASCII type/name + */ + public static String getChunkType(DataInputStream distream) { + try { + distream.mark(8); + /* int length = */distream.readInt(); + int type = distream.readInt(); + distream.reset(); + + return typeIntToString(type); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + + private static String typeIntToString(int type) { + String typeString = ""; + typeString += (char) (type >> 24); + typeString += (char) ((type >> 16) & 0xff); + typeString += (char) ((type >> 8) & 0xff); + typeString += (char) (type & 0xff); + return typeString; + } + + /** + * Skips the next chunk from the input stream. + * @param distream the input stream + * @return true if skipping successful, false otherwise + */ + public static boolean skipChunk(DataInputStream distream) { + try { + int length = distream.readInt(); + int type = distream.readInt(); + // is this really faster than reading? + int skipped = distream.skipBytes(length); + int crc = distream.readInt(); + if (skipped != length) { + log.warn("Incorrect number of bytes skipped."); + return false; + } + return true; + } catch (Exception e) { + log.warn(e.getMessage()); + return false; + } + } +}