| /* |
| * 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.ImageFormat; |
| import org.apache.commons.imaging.ImageReadException; |
| import org.apache.commons.imaging.ImageWriteException; |
| import org.apache.commons.imaging.Sanselan; |
| import org.apache.commons.imaging.SanselanConstants; |
| import org.apache.commons.imaging.SanselanTest; |
| 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 SanselanTest |
| { |
| 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 ImageFormat format; |
| public final boolean canRead; |
| public final boolean canWrite; |
| public final int colorSupport; |
| public final boolean identicalSecondWrite; |
| |
| public FormatInfo(ImageFormat format, boolean canRead, |
| boolean canWrite, int colorSupport, |
| final boolean identicalSecondWrite) |
| { |
| this.canRead = canRead; |
| this.canWrite = canWrite; |
| this.colorSupport = colorSupport; |
| this.format = format; |
| this.identicalSecondWrite = identicalSecondWrite; |
| } |
| } |
| |
| private static final FormatInfo FORMAT_INFOS[] = { // |
| new FormatInfo(ImageFormat.IMAGE_FORMAT_PNG, true, true, |
| COLOR_FULL_RGB, true), // |
| new FormatInfo(ImageFormat.IMAGE_FORMAT_GIF, true, true, |
| COLOR_LIMITED_INDEX, true), // |
| new FormatInfo(ImageFormat.IMAGE_FORMAT_ICO, true, true, |
| COLOR_FULL_RGB, true), // |
| new FormatInfo(ImageFormat.IMAGE_FORMAT_TIFF, true, true, |
| COLOR_FULL_RGB, true), // |
| new FormatInfo(ImageFormat.IMAGE_FORMAT_JPEG, true, false, |
| COLOR_FULL_RGB, true), // |
| new FormatInfo(ImageFormat.IMAGE_FORMAT_BMP, true, true, |
| COLOR_FULL_RGB, true), // |
| new FormatInfo(ImageFormat.IMAGE_FORMAT_PSD, true, false, |
| COLOR_FULL_RGB, true), // |
| new FormatInfo(ImageFormat.IMAGE_FORMAT_PBM, true, true, |
| COLOR_BITMAP, true), // |
| new FormatInfo(ImageFormat.IMAGE_FORMAT_PGM, true, true, |
| COLOR_GRAYSCALE, true), // |
| new FormatInfo(ImageFormat.IMAGE_FORMAT_PPM, true, true, |
| COLOR_FULL_RGB, true), // |
| // new FormatInfo(ImageFormat.IMAGE_FORMAT_PNM, true, true, |
| // COLOR_FULL_RGB, true), // |
| new FormatInfo(ImageFormat.IMAGE_FORMAT_TGA, false, false, |
| COLOR_FULL_RGB, true), // |
| new FormatInfo(ImageFormat.IMAGE_FORMAT_WBMP, true, true, |
| COLOR_BITMAP, true), // |
| new FormatInfo(ImageFormat.IMAGE_FORMAT_PCX, true, true, |
| COLOR_FULL_RGB, true), // |
| new FormatInfo(ImageFormat.IMAGE_FORMAT_DCX, true, true, |
| COLOR_FULL_RGB, true), // |
| new FormatInfo(ImageFormat.IMAGE_FORMAT_XBM, true, true, |
| COLOR_BITMAP, false), // |
| new FormatInfo(ImageFormat.IMAGE_FORMAT_XPM, true, true, |
| COLOR_FULL_RGB, false), // |
| }; |
| |
| private BufferedImage createArgbBitmapImage(int width, int height) |
| { |
| 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. |
| int modulator = y + 2; // make sure lines vary. |
| int argb = (x + y) % modulator == 0 ? 0xff000000 : 0xffffffff; |
| result.setRGB(x, y, argb); |
| } |
| return result; |
| } |
| |
| private BufferedImage createBitmapBitmapImage(int width, int height) |
| { |
| 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. |
| int modulator = y + 2; // make sure lines vary. |
| int argb = (x + y) % modulator == 0 ? 0xff000000 : 0xffffffff; |
| result.setRGB(x, y, argb); |
| } |
| return result; |
| } |
| |
| private BufferedImage createArgbGrayscaleImage(int width, int height) |
| { |
| BufferedImage result = new BufferedImage(width, height, |
| BufferedImage.TYPE_INT_ARGB); |
| for (int x = 0; x < width; x++) |
| for (int y = 0; y < height; y++) |
| { |
| int value = (256 * (x + y)) / (width + height); |
| int argb = (0xff << 24) | (value << 16) | (value << 8) |
| | (value << 0); |
| |
| result.setRGB(x, y, argb); |
| } |
| return result; |
| } |
| |
| private BufferedImage createGrayscaleGrayscaleImage(int width, int height) |
| { |
| BufferedImage result = new BufferedImage(width, height, |
| BufferedImage.TYPE_BYTE_GRAY); |
| for (int x = 0; x < width; x++) |
| for (int y = 0; y < height; y++) |
| { |
| int value = (256 * (x + y)) / (width + height); |
| int argb = (0xff << 24) | (value << 16) | (value << 8) |
| | (value << 0); |
| |
| result.setRGB(x, y, argb); |
| } |
| return result; |
| } |
| |
| private BufferedImage createLimitedColorImage(int width, int height) |
| { |
| int colors[] = { 0xffffffff, 0xff000000, 0xfff00000, 0xff0000ff, |
| 0xff123456, 0xfffefeff, 0xff7f817f, }; |
| |
| BufferedImage result = new BufferedImage(width, height, |
| BufferedImage.TYPE_INT_ARGB); |
| for (int x = 0; x < width; x++) |
| for (int y = 0; y < height; y++) |
| { |
| int argb = colors[(x + y) % colors.length]; |
| result.setRGB(x, y, argb); |
| } |
| return result; |
| } |
| |
| private BufferedImage createFullColorImage(int width, int height) |
| { |
| BufferedImage result = new BufferedImage(width, height, |
| BufferedImage.TYPE_INT_ARGB); |
| for (int x = 0; x < width; x++) |
| for (int y = 0; y < height; y++) |
| { |
| int red = (x * 255) / width; |
| int green = (y * 255) / height; |
| int blue = ((x + y) * 255) / (width + height); |
| int argb = (0xff << 24) | (red << 16) | (green << 8) |
| | (blue << 0); |
| result.setRGB(x, y, argb); |
| } |
| return result; |
| } |
| |
| private void compareImagesExact(BufferedImage a, 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(BufferedImage a, BufferedImage b, 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++) |
| { |
| int a_argb = a.getRGB(x, y); |
| 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(int a, int b) |
| { |
| int aAlpha = 0xff & (a >> 24); |
| int aRed = 0xff & (a >> 16); |
| int aGreen = 0xff & (a >> 8); |
| int aBlue = 0xff & (a >> 0); |
| int bAlpha = 0xff & (b >> 24); |
| int bRed = 0xff & (b >> 16); |
| int bGreen = 0xff & (b >> 8); |
| int bBlue = 0xff & (b >> 0); |
| int diff = Math.abs(aAlpha - bAlpha) + Math.abs(aRed - bRed) |
| + Math.abs(aGreen - bGreen) + Math.abs(aBlue - bBlue); |
| return diff; |
| |
| } |
| |
| private void compareFilesExact(File a, File b) throws IOException |
| { |
| assertTrue(a.exists() && a.isFile()); |
| assertTrue(b.exists() && b.isFile()); |
| assertEquals(a.length(), b.length()); |
| |
| byte aData[] = IoUtils.getFileBytes(a); |
| byte bData[] = IoUtils.getFileBytes(b); |
| |
| for (int i = 0; i < a.length(); i++) |
| { |
| int aByte = 0xff & aData[i]; |
| 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 |
| { |
| 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 (int j = 0; j < testImages.length; j++) |
| { |
| BufferedImage testImage = testImages[j]; |
| |
| for (int i = 0; i < FORMAT_INFOS.length; i++) |
| { |
| FormatInfo formatInfo = FORMAT_INFOS[i]; |
| if ((!formatInfo.canRead) || (!formatInfo.canWrite)) |
| continue; |
| |
| Debug.debug("bitmap test: " + formatInfo.format.name); |
| |
| roundtrip(formatInfo, testImage, "bitmap", true); |
| } |
| } |
| } |
| |
| public void testGrayscaleRoundtrip() throws Exception |
| { |
| 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 (int j = 0; j < testImages.length; j++) |
| { |
| BufferedImage testImage = testImages[j]; |
| |
| for (int i = 0; i < FORMAT_INFOS.length; i++) |
| { |
| FormatInfo formatInfo = FORMAT_INFOS[i]; |
| if ((!formatInfo.canRead) || (!formatInfo.canWrite)) |
| continue; |
| |
| Debug.debug("grayscale test: " + formatInfo.format.name); |
| |
| boolean imageExact = true; |
| if (formatInfo.colorSupport == COLOR_BITMAP) |
| imageExact = false; |
| |
| roundtrip(formatInfo, testImage, "gray", imageExact); |
| } |
| } |
| } |
| |
| public void testLimitedColorRoundtrip() throws Exception |
| { |
| BufferedImage testImages[] = { // |
| |
| createLimitedColorImage(1, 1), // minimal |
| createLimitedColorImage(2, 2), // |
| createLimitedColorImage(10, 10), // larger than 8 |
| createLimitedColorImage(300, 300), // larger than 256 |
| }; |
| |
| for (int j = 0; j < testImages.length; j++) |
| { |
| BufferedImage testImage = testImages[j]; |
| |
| for (int i = 0; i < FORMAT_INFOS.length; i++) |
| { |
| FormatInfo formatInfo = FORMAT_INFOS[i]; |
| if ((!formatInfo.canRead) || (!formatInfo.canWrite)) |
| continue; |
| |
| Debug.debug("indexable test: " + formatInfo.format.name); |
| |
| 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 |
| { |
| BufferedImage testImages[] = { // |
| |
| createFullColorImage(1, 1), // minimal |
| createFullColorImage(2, 2), // |
| createFullColorImage(10, 10), // larger than 8 |
| createFullColorImage(300, 300), // larger than 256 |
| }; |
| |
| for (int j = 0; j < testImages.length; j++) |
| { |
| BufferedImage testImage = testImages[j]; |
| |
| for (int i = 0; i < FORMAT_INFOS.length; i++) |
| { |
| FormatInfo formatInfo = FORMAT_INFOS[i]; |
| if ((!formatInfo.canRead) || (!formatInfo.canWrite)) |
| continue; |
| |
| Debug.debug("fullColor test: " + formatInfo.format.name); |
| |
| 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); |
| } |
| } |
| } |
| |
| private void roundtrip(FormatInfo formatInfo, BufferedImage testImage, |
| String tempPrefix, boolean imageExact) throws IOException, |
| ImageReadException, ImageWriteException |
| { |
| File temp1 = createTempFile(tempPrefix + ".", "." |
| + formatInfo.format.extension); |
| // Debug.debug("tempFile: " + tempFile.getName()); |
| |
| Map params = new HashMap(); |
| Sanselan.writeImage(testImage, temp1, formatInfo.format, params); |
| |
| Map readParams = new HashMap(); |
| readParams.put(SanselanConstants.BUFFERED_IMAGE_FACTORY, |
| new RgbBufferedImageFactory()); |
| BufferedImage image2 = Sanselan.getBufferedImage(temp1, readParams); |
| assertNotNull(image2); |
| |
| if (imageExact) |
| { |
| // note tolerance when comparing grayscale images |
| // BufferedImages of |
| compareImagesExact(testImage, image2); |
| } |
| |
| if (formatInfo.identicalSecondWrite) |
| { |
| File temp2 = createTempFile(tempPrefix + ".", "." |
| + formatInfo.format.extension); |
| // Debug.debug("tempFile: " + tempFile.getName()); |
| Sanselan.writeImage(image2, temp2, formatInfo.format, params); |
| |
| compareFilesExact(temp1, temp2); |
| } |
| } |
| |
| } |