| /* |
| * 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. |
| */ |
| |
| package org.apache.commons.imaging.roundtrip; |
| |
| import java.awt.image.BufferedImage; |
| import java.io.File; |
| import java.io.IOException; |
| import java.util.HashMap; |
| import java.util.Map; |
| |
| import org.apache.commons.imaging.ImageFormats; |
| import org.apache.commons.imaging.ImageInfo; |
| import org.apache.commons.imaging.ImageReadException; |
| import org.apache.commons.imaging.ImageWriteException; |
| import org.apache.commons.imaging.Imaging; |
| import org.apache.commons.imaging.ImagingConstants; |
| import org.apache.commons.imaging.ImagingTest; |
| import org.apache.commons.imaging.PixelDensity; |
| import org.apache.commons.imaging.common.RgbBufferedImageFactory; |
| import org.apache.commons.imaging.util.Debug; |
| import org.apache.commons.imaging.util.IoUtils; |
| |
| public class RoundtripTest extends ImagingTest { |
| private static final int COLOR_FULL_RGB = 0; |
| private static final int COLOR_LIMITED_INDEX = 1; |
| private static final int COLOR_GRAYSCALE = 2; |
| private static final int COLOR_BITMAP = 3; |
| |
| private static class FormatInfo { |
| |
| public final ImageFormats format; |
| public final boolean canRead; |
| public final boolean canWrite; |
| public final int colorSupport; |
| public final boolean identicalSecondWrite; |
| public final boolean preservesResolution; |
| |
| public FormatInfo(final ImageFormats format, final boolean canRead, |
| final boolean canWrite, final int colorSupport, |
| final boolean identicalSecondWrite, |
| final boolean preservesResolution) { |
| this.canRead = canRead; |
| this.canWrite = canWrite; |
| this.colorSupport = colorSupport; |
| this.format = format; |
| this.identicalSecondWrite = identicalSecondWrite; |
| this.preservesResolution = preservesResolution; |
| } |
| } |
| |
| private static final FormatInfo FORMAT_INFOS[] = { // |
| new FormatInfo(ImageFormats.PNG, true, true, |
| COLOR_FULL_RGB, true, true), // |
| new FormatInfo(ImageFormats.GIF, true, true, |
| COLOR_LIMITED_INDEX, true, false), // |
| new FormatInfo(ImageFormats.ICO, true, true, |
| COLOR_FULL_RGB, true, true), // |
| new FormatInfo(ImageFormats.TIFF, true, true, |
| COLOR_FULL_RGB, true, true), // |
| new FormatInfo(ImageFormats.JPEG, true, false, |
| COLOR_FULL_RGB, true, true), // |
| new FormatInfo(ImageFormats.BMP, true, true, |
| COLOR_FULL_RGB, true, true), // |
| new FormatInfo(ImageFormats.PSD, true, false, |
| COLOR_FULL_RGB, true, true), // |
| new FormatInfo(ImageFormats.PBM, true, true, |
| COLOR_BITMAP, true, false), // |
| new FormatInfo(ImageFormats.PGM, true, true, |
| COLOR_GRAYSCALE, true, false), // |
| new FormatInfo(ImageFormats.PPM, true, true, |
| COLOR_FULL_RGB, true, false), // |
| new FormatInfo(ImageFormats.PAM, true, true, |
| COLOR_FULL_RGB, true, false),// |
| // new FormatInfo(ImageFormat.IMAGE_FORMAT_PNM, true, true, |
| // COLOR_FULL_RGB, true), // |
| new FormatInfo(ImageFormats.TGA, false, false, |
| COLOR_FULL_RGB, true, true), // |
| new FormatInfo(ImageFormats.WBMP, true, true, |
| COLOR_BITMAP, true, false), // |
| new FormatInfo(ImageFormats.PCX, true, true, |
| COLOR_FULL_RGB, true, true), // |
| new FormatInfo(ImageFormats.DCX, true, true, |
| COLOR_FULL_RGB, true, true), // |
| new FormatInfo(ImageFormats.XBM, true, true, |
| COLOR_BITMAP, false, false), // |
| new FormatInfo(ImageFormats.XPM, true, true, |
| COLOR_FULL_RGB, false, false), // |
| }; |
| |
| private BufferedImage createArgbBitmapImage(final int width, final int height) { |
| final BufferedImage result = new BufferedImage(width, height, |
| BufferedImage.TYPE_INT_ARGB); |
| for (int x = 0; x < width; x++) { |
| for (int y = 0; y < height; y++) { |
| // alternating black and white. |
| final int modulator = y + 2; // make sure lines vary. |
| final int argb = (x + y) % modulator == 0 ? 0xff000000 : 0xffffffff; |
| result.setRGB(x, y, argb); |
| } |
| } |
| return result; |
| } |
| |
| private BufferedImage createBitmapBitmapImage(final int width, final int height) { |
| final BufferedImage result = new BufferedImage(width, height, |
| BufferedImage.TYPE_BYTE_BINARY); |
| for (int x = 0; x < width; x++) { |
| for (int y = 0; y < height; y++) { |
| // alternating black and white. |
| final int modulator = y + 2; // make sure lines vary. |
| final int argb = (x + y) % modulator == 0 ? 0xff000000 : 0xffffffff; |
| result.setRGB(x, y, argb); |
| } |
| } |
| return result; |
| } |
| |
| private BufferedImage createArgbGrayscaleImage(final int width, final int height) { |
| final BufferedImage result = new BufferedImage(width, height, |
| BufferedImage.TYPE_INT_ARGB); |
| for (int x = 0; x < width; x++) { |
| for (int y = 0; y < height; y++) { |
| final int value = (256 * (x + y)) / (width + height); |
| final int argb = (0xff << 24) | (value << 16) | (value << 8) |
| | (value << 0); |
| |
| result.setRGB(x, y, argb); |
| } |
| } |
| return result; |
| } |
| |
| private BufferedImage createGrayscaleGrayscaleImage(final int width, final int height) { |
| final BufferedImage result = new BufferedImage(width, height, |
| BufferedImage.TYPE_BYTE_GRAY); |
| for (int x = 0; x < width; x++) { |
| for (int y = 0; y < height; y++) { |
| final int value = (256 * (x + y)) / (width + height); |
| final int argb = (0xff << 24) | (value << 16) | (value << 8) |
| | (value << 0); |
| |
| result.setRGB(x, y, argb); |
| } |
| } |
| return result; |
| } |
| |
| private BufferedImage createLimitedColorImage(final int width, final int height) { |
| final int colors[] = { 0xffffffff, 0xff000000, 0xfff00000, 0xff0000ff, |
| 0xff123456, 0xfffefeff, 0xff7f817f, }; |
| |
| final BufferedImage result = new BufferedImage(width, height, |
| BufferedImage.TYPE_INT_ARGB); |
| for (int x = 0; x < width; x++) { |
| for (int y = 0; y < height; y++) { |
| final int argb = colors[(x + y) % colors.length]; |
| result.setRGB(x, y, argb); |
| } |
| } |
| return result; |
| } |
| |
| private BufferedImage createFullColorImage(final int width, final int height) { |
| final BufferedImage result = new BufferedImage(width, height, |
| BufferedImage.TYPE_INT_ARGB); |
| for (int x = 0; x < width; x++) { |
| for (int y = 0; y < height; y++) { |
| final int red = (x * 255) / width; |
| final int green = (y * 255) / height; |
| final int blue = ((x + y) * 255) / (width + height); |
| final int argb = (0xff << 24) | (red << 16) | (green << 8) |
| | (blue << 0); |
| result.setRGB(x, y, argb); |
| } |
| } |
| return result; |
| } |
| |
| private void compareImagesExact(final BufferedImage a, final BufferedImage b) { |
| compareImages(a, b, 0); |
| } |
| |
| // private void compareImagesOffByOne(BufferedImage a, BufferedImage b) |
| // { |
| // compareImages(a, b, 3); // one bit of rounding error for each channel |
| // } |
| |
| private void compareImages(final BufferedImage a, final BufferedImage b, final int tolerance) { |
| assertEquals(a.getWidth(), b.getWidth()); |
| assertEquals(a.getHeight(), b.getHeight()); |
| |
| for (int x = 0; x < a.getWidth(); x++) { |
| for (int y = 0; y < a.getHeight(); y++) { |
| final int a_argb = a.getRGB(x, y); |
| final int b_argb = b.getRGB(x, y); |
| if (a_argb != b_argb) { |
| if (calculateARGBDistance(a_argb, b_argb) <= tolerance) |
| { |
| continue; // ignore. |
| } |
| } |
| if (a_argb != b_argb) { |
| Debug.debug("width", a.getWidth()); |
| Debug.debug("height", a.getHeight()); |
| Debug.debug("distance", |
| calculateARGBDistance(a_argb, b_argb)); |
| Debug.debug("x", x); |
| Debug.debug("y", y); |
| Debug.debug("a_argb", |
| a_argb + " (0x" + Integer.toHexString(a_argb) + ")"); |
| Debug.debug("b_argb", |
| b_argb + " (0x" + Integer.toHexString(b_argb) + ")"); |
| } |
| assertEquals(a_argb, b_argb); |
| } |
| } |
| } |
| |
| private int calculateARGBDistance(final int a, final int b) { |
| final int aAlpha = 0xff & (a >> 24); |
| final int aRed = 0xff & (a >> 16); |
| final int aGreen = 0xff & (a >> 8); |
| final int aBlue = 0xff & (a >> 0); |
| final int bAlpha = 0xff & (b >> 24); |
| final int bRed = 0xff & (b >> 16); |
| final int bGreen = 0xff & (b >> 8); |
| final int bBlue = 0xff & (b >> 0); |
| final int diff = Math.abs(aAlpha - bAlpha) + Math.abs(aRed - bRed) |
| + Math.abs(aGreen - bGreen) + Math.abs(aBlue - bBlue); |
| return diff; |
| |
| } |
| |
| private void compareFilesExact(final File a, final File b) throws IOException { |
| assertTrue(a.exists() && a.isFile()); |
| assertTrue(b.exists() && b.isFile()); |
| assertEquals(a.length(), b.length()); |
| |
| final byte aData[] = IoUtils.getFileBytes(a); |
| final byte bData[] = IoUtils.getFileBytes(b); |
| |
| for (int i = 0; i < a.length(); i++) { |
| final int aByte = 0xff & aData[i]; |
| final int bByte = 0xff & bData[i]; |
| |
| if (aByte != bByte) { |
| Debug.debug("a", a); |
| Debug.debug("b", b); |
| Debug.debug("i", i); |
| Debug.debug("aByte", |
| aByte + " (0x" + Integer.toHexString(aByte) + ")"); |
| Debug.debug("bByte", |
| bByte + " (0x" + Integer.toHexString(bByte) + ")"); |
| } |
| assertEquals(aByte, bByte); |
| } |
| } |
| |
| public void testBitmapRoundtrip() throws Exception { |
| final BufferedImage testImages[] = { // |
| |
| createArgbBitmapImage(1, 1), // minimal |
| createArgbBitmapImage(2, 2), // |
| createArgbBitmapImage(10, 10), // larger than 8 |
| createArgbBitmapImage(300, 300), // larger than 256 |
| |
| createBitmapBitmapImage(1, 1), // minimal |
| createBitmapBitmapImage(2, 2), // |
| createBitmapBitmapImage(10, 10), // larger than 8 |
| createBitmapBitmapImage(300, 300), // larger than 256 |
| }; |
| |
| for (final BufferedImage testImage : testImages) { |
| for (final FormatInfo element : FORMAT_INFOS) { |
| final FormatInfo formatInfo = element; |
| if ((!formatInfo.canRead) || (!formatInfo.canWrite)) { |
| continue; |
| } |
| |
| Debug.debug("bitmap test: " + formatInfo.format.getName()); |
| |
| roundtrip(formatInfo, testImage, "bitmap", true); |
| } |
| } |
| } |
| |
| public void testGrayscaleRoundtrip() throws Exception { |
| final BufferedImage testImages[] = { // |
| |
| createArgbBitmapImage(1, 1), // minimal |
| createArgbGrayscaleImage(2, 2), // |
| createArgbGrayscaleImage(10, 10), // larger than 8 |
| createArgbGrayscaleImage(300, 300), // larger than 256 |
| |
| createGrayscaleGrayscaleImage(1, 1), // minimal |
| createGrayscaleGrayscaleImage(2, 2), // |
| createGrayscaleGrayscaleImage(10, 10), // larger than 8 |
| createGrayscaleGrayscaleImage(300, 300), // larger than 256 |
| }; |
| |
| for (final BufferedImage testImage : testImages) { |
| for (final FormatInfo element : FORMAT_INFOS) { |
| final FormatInfo formatInfo = element; |
| if ((!formatInfo.canRead) || (!formatInfo.canWrite)) { |
| continue; |
| } |
| |
| Debug.debug("grayscale test: " + formatInfo.format.getName()); |
| |
| boolean imageExact = true; |
| if (formatInfo.colorSupport == COLOR_BITMAP) { |
| imageExact = false; |
| } |
| |
| roundtrip(formatInfo, testImage, "gray", imageExact); |
| } |
| } |
| } |
| |
| public void testLimitedColorRoundtrip() throws Exception { |
| final BufferedImage testImages[] = { // |
| |
| createLimitedColorImage(1, 1), // minimal |
| createLimitedColorImage(2, 2), // |
| createLimitedColorImage(10, 10), // larger than 8 |
| createLimitedColorImage(300, 300), // larger than 256 |
| }; |
| |
| for (final BufferedImage testImage : testImages) { |
| for (final FormatInfo element : FORMAT_INFOS) { |
| final FormatInfo formatInfo = element; |
| if ((!formatInfo.canRead) || (!formatInfo.canWrite)) { |
| continue; |
| } |
| |
| Debug.debug("indexable test: " + formatInfo.format.getName()); |
| |
| boolean imageExact = true; |
| if (formatInfo.colorSupport == COLOR_BITMAP) { |
| imageExact = false; |
| } |
| if (formatInfo.colorSupport == COLOR_GRAYSCALE) { |
| imageExact = false; |
| } |
| |
| roundtrip(formatInfo, testImage, "indexable", imageExact); |
| } |
| } |
| } |
| |
| public void testFullColorRoundtrip() throws Exception { |
| final BufferedImage testImages[] = { // |
| |
| createFullColorImage(1, 1), // minimal |
| createFullColorImage(2, 2), // |
| createFullColorImage(10, 10), // larger than 8 |
| createFullColorImage(300, 300), // larger than 256 |
| }; |
| |
| for (final BufferedImage testImage : testImages) { |
| for (final FormatInfo element : FORMAT_INFOS) { |
| final FormatInfo formatInfo = element; |
| if ((!formatInfo.canRead) || (!formatInfo.canWrite)) { |
| continue; |
| } |
| |
| Debug.debug("fullColor test: " + formatInfo.format.getName()); |
| |
| boolean imageExact = true; |
| if (formatInfo.colorSupport == COLOR_BITMAP) { |
| imageExact = false; |
| } |
| if (formatInfo.colorSupport == COLOR_GRAYSCALE) { |
| imageExact = false; |
| } |
| if (formatInfo.colorSupport == COLOR_LIMITED_INDEX) { |
| imageExact = false; |
| } |
| |
| roundtrip(formatInfo, testImage, "fullColor", imageExact); |
| } |
| } |
| } |
| |
| public void testPixelDensityRoundtrip() throws IOException, |
| ImageReadException, ImageWriteException { |
| final BufferedImage testImage = createFullColorImage(2, 2); |
| for (final FormatInfo formatInfo : FORMAT_INFOS) { |
| if (!formatInfo.canRead || !formatInfo.canWrite || !formatInfo.preservesResolution) { |
| continue; |
| } |
| |
| Debug.debug("pixel density test: " + formatInfo.format.getName()); |
| |
| final File temp1 = createTempFile("pixeldensity.", "." |
| + formatInfo.format.getExtension()); |
| |
| final Map<String,Object> params = new HashMap<String,Object>(); |
| final PixelDensity pixelDensity = PixelDensity.createFromPixelsPerInch(75, 150); |
| params.put(ImagingConstants.PARAM_KEY_PIXEL_DENSITY, pixelDensity); |
| Imaging.writeImage(testImage, temp1, formatInfo.format, params); |
| |
| final ImageInfo imageInfo = Imaging.getImageInfo(temp1); |
| if (imageInfo == null) { |
| continue; |
| } |
| final int xReadDPI = imageInfo.getPhysicalWidthDpi(); |
| final int yReadDPI = imageInfo.getPhysicalHeightDpi(); |
| // allow a 5% margin of error in storage and conversion |
| assertTrue("horizontal pixel density stored wrongly for " + formatInfo.format + |
| " in=" + pixelDensity.horizontalDensityInches() + ", out=" + xReadDPI, |
| Math.abs((xReadDPI - pixelDensity.horizontalDensityInches()) / |
| pixelDensity.horizontalDensityInches()) <= 0.05); |
| assertTrue("vertical pixel density stored wrongly for " + formatInfo.format + |
| " in=" + pixelDensity.verticalDensityInches() + ", out=" + yReadDPI, |
| Math.abs((yReadDPI - pixelDensity.verticalDensityInches()) / |
| pixelDensity.verticalDensityInches()) <= 0.05); |
| } |
| } |
| |
| private void roundtrip(final FormatInfo formatInfo, final BufferedImage testImage, |
| final String tempPrefix, final boolean imageExact) throws IOException, |
| ImageReadException, ImageWriteException { |
| final File temp1 = createTempFile(tempPrefix + ".", "." |
| + formatInfo.format.getExtension()); |
| Debug.debug("tempFile: " + temp1.getName()); |
| |
| final Map<String,Object> params = new HashMap<String,Object>(); |
| Imaging.writeImage(testImage, temp1, formatInfo.format, params); |
| |
| final Map<String,Object> readParams = new HashMap<String,Object>(); |
| readParams.put(ImagingConstants.BUFFERED_IMAGE_FACTORY, |
| new RgbBufferedImageFactory()); |
| final BufferedImage image2 = Imaging.getBufferedImage(temp1, readParams); |
| assertNotNull(image2); |
| |
| if (imageExact) { |
| // note tolerance when comparing grayscale images |
| // BufferedImages of |
| compareImagesExact(testImage, image2); |
| } |
| |
| if (formatInfo.identicalSecondWrite) { |
| final File temp2 = createTempFile(tempPrefix + ".", "." |
| + formatInfo.format.getExtension()); |
| // Debug.debug("tempFile: " + tempFile.getName()); |
| Imaging.writeImage(image2, temp2, formatInfo.format, params); |
| |
| compareFilesExact(temp1, temp2); |
| } |
| } |
| |
| } |