Created
September 26, 2010 03:38
-
-
Save nimms/597574 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package au.com.sdx.plan_processing.service.impl; | |
/** | |
* imports and logging statements elided | |
*/ | |
@SuppressWarnings("restriction") | |
public class TiffProcessor implements IPlanImageService { | |
private static final int BLACK = 0; | |
private static final int WHITE = 1; | |
private static final int LINE_LENGTH = 30; // number of pixels in a row to | |
private static final int NUM_POTENTIAL_WATERMARKS = 15; | |
private final File imageFile; | |
/** | |
* Takes in an Tiff image from our plan archive and applies a number of image processing steps to it | |
* and then bundles it into a pdf | |
* @return The location of the processed pdf | |
*/ | |
@Override | |
public String processPlanFile() throws OperationNotSupportedException, IOException, WrappedException { | |
//vars to keep track of doc info | |
int numPages = 0; | |
float width = 0; | |
float height = 0; | |
boolean resetDimensions = false; | |
List<BufferedImage> images = loadAllImagesInTiff(imageFile.getAbsolutePath()); | |
File workDirectory = new File(imageFile.getParent()); | |
for(BufferedImage image : images) { | |
if(needsInversion(image)) | |
invertImage(image); | |
removeDots(image); | |
if(image.getWidth() > image.getHeight()) { | |
image = rotateImage90DegreesCW(image); | |
resetDimensions = true; | |
} | |
String imageName = workDirectory.getAbsolutePath() + "/" + ++numPages + ".tiff"; | |
writeImage(image, imageName); //writes the image to a working dir since we can't reuse the stream to write directly to the pdf output | |
} | |
PdfProcessor service = new PdfProcessor(); | |
String pdfPath = service.createPdf(workDirectory, "output.pdf", width, height); | |
return pdfPath; | |
} | |
/** | |
* A tiff is essentially a directory structure of compressed images. | |
* Load all images in a tiff and return them as a list of buffered images | |
* @param fileName | |
* @return | |
* @throws IOException | |
*/ | |
public List<BufferedImage> loadAllImagesInTiff(String fileName) throws IOException, OperationNotSupportedException { | |
Iterator<ImageReader> readers = ImageIO.getImageReadersByFormatName("tiff"); | |
TIFFImageReader reader = (TIFFImageReader) readers.next(); | |
File tiff = new File(fileName); | |
ImageInputStream iis = ImageIO.createImageInputStream(tiff); | |
reader.setInput(iis, false); | |
int numImages = reader.getNumImages(true); | |
List<BufferedImage> images = new ArrayList<BufferedImage>(); | |
for(int i = 0; i < numImages; i ++) { | |
TIFFImageMetadata metaData = (TIFFImageMetadata) reader.getImageMetadata(i); | |
TIFFIFD rootIFD = metaData.getRootIFD(); | |
TIFFField field = rootIFD.getTIFFField(262); | |
// we are only working with a few types of tiffs. | |
//Any other photometric interpretation is probably a corrupt image in our archive, | |
//so an exception is thrown to be caught and logged elsewhere. | |
if(field.getAsInt(0) >= 2) | |
throw new OperationNotSupportedException("Unsupported Photometric Interpretation: " + field.getAsInt(0)); | |
images.add(reader.read(i)); | |
} | |
iis.close(); | |
return images; | |
} | |
/** | |
* Manual check to see what photometric interpretation a tiff is using. Given we can't | |
* trust the validity of the tiff data in our archive, | |
* we will take a count of the number of ones and zeros and test | |
* for ourselves. | |
* | |
* The two photometric interpretations we handle are MIN_IS_WHITE and MIN_IS_BLACK. in MIN_IS_BLACK, | |
* we should see far more zeros than ones since black is represented by a zero. If this is the case, | |
* we will later invert the image and then save it as a MIN_IS_WHITE | |
* @param image | |
* @return whether or not we should invert the tiff | |
*/ | |
public boolean needsInversion(BufferedImage image) { | |
WritableRaster raster = image.getRaster(); | |
int zeros = 0; | |
int ones = 0; | |
for(int y = 0; y < raster.getHeight(); y++) { | |
for(int x = 0; x < raster.getWidth(); x++) { | |
int pixelValue = raster.getSample(x, y, 0); | |
if(pixelValue == 0) | |
zeros ++; | |
else | |
ones++; | |
} | |
} | |
if(ones < zeros) | |
return true; | |
else | |
return false; | |
} | |
/** | |
* removes dust specks from an image | |
* | |
* @param image | |
*/ | |
public void removeDots(BufferedImage image) { | |
WritableRaster raster = image.getRaster(); | |
int height = raster.getHeight(); | |
int width = raster.getWidth(); | |
for (int y = 0; y <= height; y++) { | |
for (int x = 0; x <= width; x++) { | |
int pixelValue = raster.getSample(x, y, 0); | |
if ((pixelValue == BLACK) && isDot(x, y, raster)) { | |
raster.setSample(x, y, 0, WHITE); | |
} | |
} | |
} | |
image.setData(raster); | |
} | |
/** | |
* Rotates an image 90 degrees clockwise. No need to implement counter clockwise at this point | |
* due to the nature of our image archive | |
*/ | |
public BufferedImage rotateImage90DegreesCW(BufferedImage image) { | |
int width = image.getWidth(); | |
int height = image.getHeight(); | |
BufferedImage rotatedImage = new BufferedImage(height, width, | |
image.getType()); | |
for (int i = 0; i < width; i++) | |
for (int j = 0; j < height; j++) | |
rotatedImage.setRGB(j, width - 1 - i, image.getRGB(i, j)); | |
return rotatedImage; | |
} | |
/** | |
* given a width or height that we wish to operate on the middle portion of, | |
* this function determines the starting point. So if we wish to operate on | |
* the middle 80% of a 1000px image, the starting point will be at 100px and | |
* the ending point will be at 900px | |
* | |
* @param dimension | |
* @param percentage | |
* @return | |
*/ | |
private int startingPoint(int dimension, double percentage) { | |
int numPixels = ((int) (dimension * percentage)); | |
return (dimension - numPixels) / 2; | |
} | |
/** | |
* given a width or height that we wish to operate on the middle portion of, | |
* this function determines the starting point. So if we wish to operate on | |
* the middle 80% of a 1000px image, the starting point will be at 100px and | |
* the ending point will be at 900px | |
* | |
* @param dimension | |
* @param percentage | |
* @return | |
*/ | |
private int finishingPoint(int dimension, double percentage) { | |
int numPixels = ((int) (dimension * percentage)); | |
int start = startingPoint(dimension, percentage); | |
return numPixels + start; | |
} | |
/** | |
* Uses a simple line detection algorithm to identify possible | |
* watermarks in an image | |
* | |
* @param image | |
* @return | |
*/ | |
public boolean isWatermarked(BufferedImage image) { | |
int potentialWaterMarks = 0; | |
WritableRaster raster = image.getRaster(); | |
int height = raster.getHeight(); | |
int width = raster.getWidth(); | |
for (int y = 0; y < height; y++) { | |
for (int x = 0; x < width; x++) { | |
int pixelValue = raster.getSample(x, y, 0); | |
if (pixelValue == BLACK) | |
if (isDottedLine(x, y, raster)) | |
potentialWaterMarks++; | |
} | |
} | |
if (potentialWaterMarks > NUM_POTENTIAL_WATERMARKS) { | |
return true; | |
} else { | |
return false; | |
} | |
} | |
/** | |
* check all the pixels in a 1 pixel proximity of the pixel to see if any of | |
* them are black | |
* | |
* @param x | |
* @param y | |
* @param raster | |
* @return | |
*/ | |
private boolean isDot(int x, int y, Raster raster) { | |
for (int height = y - 1; height <= y + 1; height++) { | |
for (int width = (x - 1); width <= (x + 1); width++) { | |
if ((width == x) && (height == y)) { | |
if (raster.getSample(width, height, 0) == WHITE) | |
return false; | |
} else { | |
if (raster.getSample(width, height, 0) == BLACK) | |
return false; | |
} | |
} | |
} | |
return true; | |
} | |
/** | |
* From a black pixel, move forward in jumps of 2 px's checking to see if we're looking | |
* at a dotted line | |
*/ | |
private boolean isDottedLine(int x, int y, Raster raster) { | |
for (int i = x + 2; i < x + LINE_LENGTH; i += 2) { | |
if (!isDot(i, y, raster)) { | |
return false; | |
} | |
} | |
return true; | |
} | |
/** | |
* For each black pixel, set it to white otherwise set it to black | |
* | |
* @param raster | |
* @return | |
*/ | |
public void invertImage(BufferedImage image) { | |
WritableRaster raster = image.getRaster(); | |
for (int y = 0; y < raster.getHeight(); y++) { | |
for (int x = 0; x < raster.getWidth(); x++) { | |
if (raster.getSample(x, y, 0) == 0) | |
raster.setSample(x, y, 0, 1); | |
else | |
raster.setSample(x, y, 0, 0); | |
} | |
} | |
image.setData(raster); | |
} | |
/** | |
* Writes a buffered image to an output file. Note that this will overwrite | |
* a file if it already exists | |
* | |
* @param image | |
* @param outputFile | |
* @throws IOException | |
*/ | |
public void writeImage(BufferedImage image, String outputFile) | |
throws IOException { | |
Iterator<ImageWriter> writers = ImageIO | |
.getImageWritersByFormatName("tiff"); | |
ImageWriter writer = writers.next(); | |
TIFFImageWriteParam param = (TIFFImageWriteParam) writer | |
.getDefaultWriteParam(); | |
param.setTilingMode(TIFFImageWriteParam.MODE_DISABLED); | |
param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT); | |
param.setCompressionType("CCITT T.6"); | |
TIFFImageMetadata metadata = getMetadata(writer, image, param); | |
File f = new File(outputFile); | |
if (f.exists()) { | |
f.delete(); | |
} | |
ImageOutputStream ios = ImageIO.createImageOutputStream(f); | |
writer.setOutput(ios); | |
writer.write(null, new IIOImage(image, null, metadata), param); | |
ios.close(); | |
} | |
/** | |
* The meta data we use for writing tiffs | |
*/ | |
private TIFFImageMetadata getMetadata(ImageWriter imageWriter, | |
BufferedImage image, ImageWriteParam param) { | |
TIFFImageMetadata tiffMetadata = (TIFFImageMetadata) getIIOMetadata( | |
image, imageWriter, param); | |
TIFFIFD rootIFD = tiffMetadata.getRootIFD(); | |
BaselineTIFFTagSet base = BaselineTIFFTagSet.getInstance(); | |
// PhotometricInterpretation | |
rootIFD.addTIFFField(new TIFFField(base.getTag(262), 0)); | |
// Compression | |
rootIFD.addTIFFField(new TIFFField(base.getTag(259), 4)); | |
// ResolutionUnit | |
rootIFD.addTIFFField(new TIFFField(base.getTag(296), 2)); | |
// BitsPerSample | |
rootIFD.addTIFFField(new TIFFField(base.getTag(258), 1)); | |
// RowsPerStrip | |
rootIFD.addTIFFField(new TIFFField(base.getTag(278), image.getHeight())); | |
// FillOrder | |
rootIFD.addTIFFField(new TIFFField(base.getTag(266), 1)); | |
return tiffMetadata; | |
} | |
private IIOMetadata getIIOMetadata(RenderedImage image, | |
ImageWriter imageWriter, ImageWriteParam param) { | |
ImageTypeSpecifier spec = ImageTypeSpecifier | |
.createFromRenderedImage(image); | |
IIOMetadata metadata = imageWriter.getDefaultImageMetadata(spec, param); | |
return metadata; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment