Skip to content
Snippets Groups Projects
Commit 94c4f52b authored by brinn's avatar brinn
Browse files

Merge DataTypeUtil with ImageUtil.

SVN: 27042
parent 74869a17
No related branches found
No related tags found
No related merge requests found
Showing
with 244 additions and 322 deletions
common/resource/test-data/DataTypeUtilTest/gif-example.gif

23.7 KiB

common/resource/test-data/DataTypeUtilTest/jpeg-example.jpg

130 KiB

common/resource/test-data/DataTypeUtilTest/png-example.png

244 KiB

common/resource/test-data/DataTypeUtilTest/tiff-example.tiff

2.36 MiB

/*
* Copyright 2010 ETH Zuerich, CISD
*
* Licensed 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 ch.systemsx.cisd.common.utilities;
import ch.systemsx.cisd.base.io.IRandomAccessFile;
/**
* Utility methods about the type of (binary) data.
*
* @author Franz-Josef Elmer
*/
public class DataTypeUtil
{
public static final String TIFF_FILE = "tif";
public static final String PNG_FILE = "png";
public static final String JPEG_FILE = "jpg";
public static final String GIF_FILE = "gif";
private static final class MagicNumber
{
private final String fileType;
private final String[] magicHexNumbers;
private final int maxLength;
MagicNumber(String fileType, String... magicHexNumbers)
{
this.fileType = fileType;
this.magicHexNumbers = magicHexNumbers;
int length = 0;
for (String magicNumber : magicHexNumbers)
{
length = Math.max(length, magicNumber.length());
}
maxLength = length / 2;
}
public String getFileType()
{
return fileType;
}
int getMaxLength()
{
return maxLength;
}
public boolean matches(byte[] bytes)
{
StringBuilder builder = new StringBuilder();
for (int i = 0; i < maxLength; i++)
{
byte b = bytes[i];
builder.append(Integer.toHexString((b >> 4) & 0xf));
builder.append(Integer.toHexString(b & 0xf));
}
String initialBytes = builder.toString().toLowerCase();
for (String magicNumber : magicHexNumbers)
{
if (initialBytes.startsWith(magicNumber))
{
return true;
}
}
return false;
}
}
private static final class MagicNumbersManager
{
private final MagicNumber[] magicNumbers;
MagicNumbersManager(MagicNumber... magicNumbers)
{
this.magicNumbers = magicNumbers;
}
int getMaxLength()
{
int max = 0;
for (MagicNumber magicNumber : magicNumbers)
{
max = Math.max(max, magicNumber.getMaxLength());
}
return max;
}
String tryToFigureOutFileTypeOf(byte[] initialBytes)
{
for (MagicNumber magicNumber : magicNumbers)
{
if (magicNumber.matches(initialBytes))
{
return magicNumber.getFileType();
}
}
return null;
}
}
private static final MagicNumbersManager MAGIC_NUMBERS_MANAGER =
new MagicNumbersManager(new MagicNumber(GIF_FILE, "474946383961", "474946383761"),
new MagicNumber(JPEG_FILE, "ffd8ff"), new MagicNumber(PNG_FILE,
"89504e470d0a1a0a"), new MagicNumber(TIFF_FILE, "49492a00", "4d4d002a"));
/**
* Tries to figure out the file type of the specified binary content. It uses the first few
* bytes as a finger print (so-called 'magic numbers') as a heuristic to get the type of
* content. Currently only the following types are recognized: <code>gif, jpg, png, tif</code>.
*
* @param handle {@link IRandomAccessFile} which supports marking.
* @return <code>null</code> if file type couldn't be figured out.
*/
public static String tryToFigureOutFileTypeOf(IRandomAccessFile handle)
{
if (handle.markSupported() == false)
{
throw new IllegalArgumentException("Input stream does not support marking. "
+ "Wrap input stream with a BufferedInputStream to solve the problem.");
}
int maxLength = MAGIC_NUMBERS_MANAGER.getMaxLength();
handle.mark(maxLength);
byte[] initialBytes = new byte[maxLength];
handle.read(initialBytes);
handle.reset();
return MAGIC_NUMBERS_MANAGER.tryToFigureOutFileTypeOf(initialBytes);
}
/**
* Returns <code>true</code> if the <var>fileTypeOrNull</var> is a tiff file.
*/
public static boolean isTiff(String fileTypeOrNull)
{
return TIFF_FILE.equals(fileTypeOrNull);
}
/**
* Returns <code>true</code> if the <var>fileTypeOrNull</var> is a jpeg file.
*/
public static boolean isJpeg(String fileTypeOrNull)
{
return JPEG_FILE.equals(fileTypeOrNull);
}
/**
* Returns <code>true</code> if the <var>fileTypeOrNull</var> is a png file.
*/
public static boolean isPng(String fileTypeOrNull)
{
return PNG_FILE.equals(fileTypeOrNull);
}
/**
* Returns <code>true</code> if the <var>fileTypeOrNull</var> is a gif file.
*/
public static boolean isGif(String fileTypeOrNull)
{
return GIF_FILE.equals(fileTypeOrNull);
}
private DataTypeUtil()
{
}
}
/*
* Copyright 2010 ETH Zuerich, CISD
*
* Licensed 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 ch.systemsx.cisd.common.utilities;
import java.io.File;
import org.testng.AssertJUnit;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import ch.systemsx.cisd.base.io.ByteBufferRandomAccessFile;
import ch.systemsx.cisd.base.io.RandomAccessFileImpl;
/**
*
*
* @author Franz-Josef Elmer
*/
public class DataTypeUtilTest extends AssertJUnit
{
private File dir;
@BeforeMethod
public void setUp()
{
dir = new File("resource/test-data/DataTypeUtilTest");
}
@Test
public void testGif() throws Exception
{
assertFileType("gif", "gif-example.gif");
}
@Test
public void testJpg() throws Exception
{
assertFileType("jpg", "jpeg-example.jpg");
}
@Test
public void testPng() throws Exception
{
assertFileType("png", "png-example.png");
}
@Test
public void testTiff() throws Exception
{
assertFileType("tif", "tiff-example.tiff");
}
@Test
public void testFileContainingOnlyOneUmlaut() throws Exception
{
assertFileType(null, "one-umlaut.txt");
}
@Test
public void testMarkUnsupportedInputStream()
{
try
{
DataTypeUtil.tryToFigureOutFileTypeOf(new ByteBufferRandomAccessFile(1)
{
@Override
public boolean markSupported()
{
return false;
}
});
fail("IllegalArgumentException expected");
} catch (IllegalArgumentException ex)
{
assertEquals("Input stream does not support marking. "
+ "Wrap input stream with a BufferedInputStream to solve the problem.",
ex.getMessage());
}
}
@Test
public void testInputStreamAtTheBeginning() throws Exception
{
byte[] bytes = "hello world".getBytes();
ByteBufferRandomAccessFile buffer = new ByteBufferRandomAccessFile(bytes);
DataTypeUtil.tryToFigureOutFileTypeOf(buffer);
assertEquals(0, buffer.getFilePointer());
}
private void assertFileType(String expectedFileType, String fileName) throws Exception
{
RandomAccessFileImpl handle = null;
try
{
handle = new RandomAccessFileImpl(new File(dir, fileName), "r");
String type = DataTypeUtil.tryToFigureOutFileTypeOf(handle);
assertEquals(expectedFileType, type);
} finally
{
closeQuetly(handle);
}
}
private void closeQuetly(RandomAccessFileImpl handle)
{
try {
handle.close();
} catch (Exception ex)
{
// keep quiet
}
}
}
......@@ -16,11 +16,6 @@
package ch.systemsx.cisd.openbis.dss.generic.shared.utils;
import static ch.systemsx.cisd.common.utilities.DataTypeUtil.GIF_FILE;
import static ch.systemsx.cisd.common.utilities.DataTypeUtil.JPEG_FILE;
import static ch.systemsx.cisd.common.utilities.DataTypeUtil.PNG_FILE;
import static ch.systemsx.cisd.common.utilities.DataTypeUtil.TIFF_FILE;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
......@@ -58,7 +53,6 @@ import ch.systemsx.cisd.common.io.FileBasedContentNode;
import ch.systemsx.cisd.common.io.hierarchical_content.api.IHierarchicalContentNode;
import ch.systemsx.cisd.common.logging.LogCategory;
import ch.systemsx.cisd.common.logging.LogFactory;
import ch.systemsx.cisd.common.utilities.DataTypeUtil;
import ch.systemsx.cisd.imagereaders.IImageReader;
import ch.systemsx.cisd.imagereaders.IReadParams;
import ch.systemsx.cisd.imagereaders.ImageID;
......@@ -72,6 +66,14 @@ import ch.systemsx.cisd.imagereaders.ImageReaderFactory;
*/
public class ImageUtil
{
public static final String TIFF_FILE = "tif";
public static final String PNG_FILE = "png";
public static final String JPEG_FILE = "jpg";
public static final String GIF_FILE = "gif";
/**
* When a grayscale image with color depth > 8 bits has to be displayed and user has not decided
* how it should be converted, then this threshold will be used.
......@@ -90,6 +92,94 @@ public class ImageUtil
public Dimension readDimension(IRandomAccessFile handle, ImageID imageID);
}
private static final class MagicNumber
{
private final String fileType;
private final String[] magicHexNumbers;
private final int maxLength;
MagicNumber(String fileType, String... magicHexNumbers)
{
this.fileType = fileType;
this.magicHexNumbers = magicHexNumbers;
int length = 0;
for (String magicNumber : magicHexNumbers)
{
length = Math.max(length, magicNumber.length());
}
maxLength = length / 2;
}
public String getFileType()
{
return fileType;
}
int getMaxLength()
{
return maxLength;
}
public boolean matches(byte[] bytes)
{
StringBuilder builder = new StringBuilder();
for (int i = 0; i < maxLength; i++)
{
byte b = bytes[i];
builder.append(Integer.toHexString((b >> 4) & 0xf));
builder.append(Integer.toHexString(b & 0xf));
}
String initialBytes = builder.toString().toLowerCase();
for (String magicNumber : magicHexNumbers)
{
if (initialBytes.startsWith(magicNumber))
{
return true;
}
}
return false;
}
}
private static final class MagicNumbersManager
{
private final MagicNumber[] magicNumbers;
MagicNumbersManager(MagicNumber... magicNumbers)
{
this.magicNumbers = magicNumbers;
}
int getMaxLength()
{
int max = 0;
for (MagicNumber magicNumber : magicNumbers)
{
max = Math.max(max, magicNumber.getMaxLength());
}
return max;
}
String tryToFigureOutFileTypeOf(byte[] initialBytes)
{
for (MagicNumber magicNumber : magicNumbers)
{
if (magicNumber.matches(initialBytes))
{
return magicNumber.getFileType();
}
}
return null;
}
}
private static final MagicNumbersManager MAGIC_NUMBERS_MANAGER =
new MagicNumbersManager(new MagicNumber(GIF_FILE, "474946383961", "474946383761"),
new MagicNumber(JPEG_FILE, "ffd8ff"), new MagicNumber(PNG_FILE,
"89504e470d0a1a0a"), new MagicNumber(TIFF_FILE, "49492a00", "4d4d002a"));
private static final class TiffImageLoader implements ImageLoader
{
private final static int MAX_READ_AHEAD = 30000000;
......@@ -565,7 +655,7 @@ public class ImageUtil
ImageID imageID)
{
IRandomAccessFile handle = contentNode.getFileContent();
String fileType = DataTypeUtil.tryToFigureOutFileTypeOf(handle);
String fileType = tryToFigureOutFileTypeOf(handle);
return loadImageGuessingLibrary(handle, fileType, imageID);
}
......@@ -573,7 +663,7 @@ public class ImageUtil
IHierarchicalContentNode contentNode, ImageID imageID)
{
IRandomAccessFile handle = contentNode.getFileContent();
String fileType = DataTypeUtil.tryToFigureOutFileTypeOf(handle);
String fileType = tryToFigureOutFileTypeOf(handle);
return loadImageDimensionGuessingLibrary(handle, fileType, imageID);
}
......@@ -809,4 +899,59 @@ public class ImageUtil
}
}
/**
* Tries to figure out the file type of the specified binary content. It uses the first few
* bytes as a finger print (so-called 'magic numbers') as a heuristic to get the type of
* content. Currently only the following types are recognized: <code>gif, jpg, png, tif</code>.
*
* @param handle {@link IRandomAccessFile} which supports marking.
* @return <code>null</code> if file type couldn't be figured out.
*/
public static String tryToFigureOutFileTypeOf(IRandomAccessFile handle)
{
if (handle.markSupported() == false)
{
throw new IllegalArgumentException("Input stream does not support marking. "
+ "Wrap input stream with a BufferedInputStream to solve the problem.");
}
int maxLength = MAGIC_NUMBERS_MANAGER.getMaxLength();
handle.mark(maxLength);
byte[] initialBytes = new byte[maxLength];
handle.read(initialBytes);
handle.reset();
return MAGIC_NUMBERS_MANAGER.tryToFigureOutFileTypeOf(initialBytes);
}
/**
* Returns <code>true</code> if the <var>fileTypeOrNull</var> is a tiff file.
*/
public static boolean isTiff(String fileTypeOrNull)
{
return TIFF_FILE.equals(fileTypeOrNull);
}
/**
* Returns <code>true</code> if the <var>fileTypeOrNull</var> is a jpeg file.
*/
public static boolean isJpeg(String fileTypeOrNull)
{
return JPEG_FILE.equals(fileTypeOrNull);
}
/**
* Returns <code>true</code> if the <var>fileTypeOrNull</var> is a png file.
*/
public static boolean isPng(String fileTypeOrNull)
{
return PNG_FILE.equals(fileTypeOrNull);
}
/**
* Returns <code>true</code> if the <var>fileTypeOrNull</var> is a gif file.
*/
public static boolean isGif(String fileTypeOrNull)
{
return GIF_FILE.equals(fileTypeOrNull);
}
}
......@@ -29,6 +29,7 @@ import ch.rinn.restrictions.Friend;
import ch.systemsx.cisd.base.exceptions.IOExceptionUnchecked;
import ch.systemsx.cisd.base.io.ByteBufferRandomAccessFile;
import ch.systemsx.cisd.base.io.IRandomAccessFile;
import ch.systemsx.cisd.base.io.RandomAccessFileImpl;
import ch.systemsx.cisd.common.io.FileBasedContentNode;
import ch.systemsx.cisd.common.io.hierarchical_content.api.IHierarchicalContentNode;
import ch.systemsx.cisd.imagereaders.ImageReaderConstants;
......@@ -240,4 +241,94 @@ public class ImageUtilTest extends AssertJUnit
return ImageUtil.loadImage(content);
}
@Test
public void testGif() throws Exception
{
assertFileType("gif", "gif-example.gif");
}
@Test
public void testJpg() throws Exception
{
assertFileType("jpg", "jpeg-example.jpg");
}
@Test
public void testPng() throws Exception
{
assertFileType("png", "png-example.png");
}
@Test
public void testTiff() throws Exception
{
assertFileType("tif", "tiff-example.tiff");
}
@Test
public void testFileContainingOnlyOneUmlaut() throws Exception
{
assertFileType(null, "one-umlaut.txt");
}
@Test
public void testMarkUnsupportedInputStream()
{
try
{
ImageUtil.tryToFigureOutFileTypeOf(new ByteBufferRandomAccessFile(1)
{
@Override
public boolean markSupported()
{
return false;
}
});
fail("IllegalArgumentException expected");
} catch (IllegalArgumentException ex)
{
assertEquals("Input stream does not support marking. "
+ "Wrap input stream with a BufferedInputStream to solve the problem.",
ex.getMessage());
}
}
@Test
public void testInputStreamAtTheBeginning() throws Exception
{
byte[] bytes = "hello world".getBytes();
ByteBufferRandomAccessFile buffer = new ByteBufferRandomAccessFile(bytes);
ImageUtil.tryToFigureOutFileTypeOf(buffer);
assertEquals(0, buffer.getFilePointer());
}
private void assertFileType(String expectedFileType, String fileName) throws Exception
{
RandomAccessFileImpl handle = null;
try
{
handle = new RandomAccessFileImpl(new File(dir, fileName), "r");
String type = ImageUtil.tryToFigureOutFileTypeOf(handle);
assertEquals(expectedFileType, type);
} finally
{
closeQuetly(handle);
}
}
private void closeQuetly(RandomAccessFileImpl handle)
{
try {
handle.close();
} catch (Exception ex)
{
// keep quiet
}
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment