From aaa9ac6989bbe38bcf689e62fb943d34804750be Mon Sep 17 00:00:00 2001 From: Graham Triggs Date: Mon, 19 Dec 2016 10:59:28 +0000 Subject: [PATCH] =?UTF-8?q?Migrating=20image=20processor=20testers=20for?= =?UTF-8?q?=20the=20ImageIO=20implementation=20-=20stage=201,=20remove=20t?= =?UTF-8?q?he=20old=20testers=20from=20branch=20so=20build=20doesn?= =?UTF-8?q?=E2=80=99t=20break?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../imageio/JaiImageProcessorTester.java | 134 +++++++++ .../imageio/JaiImageProcessorTester2.java | 272 ++++++++++++++++++ 2 files changed, 406 insertions(+) create mode 100644 api/src/test/java/edu/cornell/mannlib/vitro/webapp/imageprocessor/imageio/JaiImageProcessorTester.java create mode 100644 api/src/test/java/edu/cornell/mannlib/vitro/webapp/imageprocessor/imageio/JaiImageProcessorTester2.java diff --git a/api/src/test/java/edu/cornell/mannlib/vitro/webapp/imageprocessor/imageio/JaiImageProcessorTester.java b/api/src/test/java/edu/cornell/mannlib/vitro/webapp/imageprocessor/imageio/JaiImageProcessorTester.java new file mode 100644 index 000000000..8dbccf723 --- /dev/null +++ b/api/src/test/java/edu/cornell/mannlib/vitro/webapp/imageprocessor/imageio/JaiImageProcessorTester.java @@ -0,0 +1,134 @@ +/* $This file is distributed under the terms of the license in /doc/license.txt$ */ + +package edu.cornell.mannlib.vitro.webapp.imageprocessor.imageio; + +import static edu.cornell.mannlib.vitro.webapp.controller.freemarker.ImageUploadController.THUMBNAIL_HEIGHT; +import static edu.cornell.mannlib.vitro.webapp.controller.freemarker.ImageUploadController.THUMBNAIL_WIDTH; + +import java.awt.Frame; +import java.awt.GridLayout; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import javax.media.jai.RenderedOp; +import javax.media.jai.operator.StreamDescriptor; + +import org.apache.log4j.Level; +import org.apache.log4j.Logger; + +import com.sun.media.jai.codec.MemoryCacheSeekableStream; + +import edu.cornell.mannlib.vitro.webapp.imageprocessor.jai.JaiImageProcessor; +import edu.cornell.mannlib.vitro.webapp.modules.imageProcessor.ImageProcessor.CropRectangle; +import edu.cornell.mannlib.vitro.webapp.modules.imageProcessor.ImageProcessor.Dimensions; + +/** + * This is not a unit test, so it is not named BlahBlahTest. + * + * Instead, it's a test harness that creates thumbnails and writes them to + * files, while also displaying them in a window on the screen. It takes human + * intervention to evaluate. + * + * This is especially true because the images on the screen look color-correct, + * but when viewed in the browser, they might not be. + */ +public class JaiImageProcessorTester extends Frame { + + /** Big enough to hold the JPEG file, certainly. */ + private final static int BUFFER_SIZE = 200 * 200 * 4; + + private final static Dimensions THUMBNAIL_SIZE = new Dimensions( + THUMBNAIL_WIDTH, THUMBNAIL_HEIGHT); + + private final static ImageCropData[] THUMBNAIL_DATA = new ImageCropData[] { + new ImageCropData("/Users/jeb228/Pictures/JimBlake_20010915.jpg", + 50, 50, 115), + new ImageCropData("/Users/jeb228/Pictures/brazil_collab.png", 600, + 250, 400), + new ImageCropData("/Users/jeb228/Pictures/wheel.png", 0, 0, 195), + new ImageCropData("/Users/jeb228/Pictures/DSC04203w-trans.gif", + 400, 1200, 800) }; + + private final JaiImageProcessor thumbnailer = new JaiImageProcessor(); + + @SuppressWarnings("deprecation") + private JaiImageProcessorTester() { + setTitle("Alpha Killer Test"); + addWindowListener(new CloseWindowListener()); + setLayout(createLayout()); + for (ImageCropData icd : THUMBNAIL_DATA) { + try { + InputStream mainStream = new FileInputStream(icd.filename); + File thumbFile = writeToTempFile(thumbnailer.cropAndScale( + mainStream, icd.crop, THUMBNAIL_SIZE)); + System.out.println(thumbFile.getAbsolutePath()); + + MemoryCacheSeekableStream thumbFileStream = new MemoryCacheSeekableStream( + new FileInputStream(thumbFile)); + RenderedOp thumbImage = StreamDescriptor.create( + thumbFileStream, null, null); + add(new javax.media.jai.widget.ImageCanvas(thumbImage)); + } catch (Exception e) { + e.printStackTrace(); + } + } + pack(); + setVisible(true); + } + + /** + * @param thumbStream Thumbnail stream + * @throws IOException + * @throws FileNotFoundException + */ + private File writeToTempFile(InputStream thumbStream) throws IOException, + FileNotFoundException { + File thumbFile = File.createTempFile("ImageUploaderThumbnailerTester", + ""); + OutputStream imageOutputStream = new FileOutputStream(thumbFile); + byte[] buffer = new byte[BUFFER_SIZE]; + int howMany = thumbStream.read(buffer); + imageOutputStream.write(buffer, 0, howMany); + imageOutputStream.close(); + return thumbFile; + } + + private GridLayout createLayout() { + GridLayout layout = new GridLayout(1, THUMBNAIL_DATA.length); + layout.setHgap(10); + return layout; + } + + @SuppressWarnings("unused") + public static void main(String[] args) { + Logger.getLogger(JaiImageProcessor.class).setLevel(Level.DEBUG); + new JaiImageProcessorTester(); + } + + private static class ImageCropData { + final String filename; + final CropRectangle crop; + + ImageCropData(String filename, int x, int y, int size) { + this.filename = filename; + this.crop = new CropRectangle(x, y, size, + size); + } + } + + private class CloseWindowListener extends WindowAdapter { + @Override + public void windowClosing(WindowEvent e) { + setVisible(false); + dispose(); + System.exit(0); + } + } +} diff --git a/api/src/test/java/edu/cornell/mannlib/vitro/webapp/imageprocessor/imageio/JaiImageProcessorTester2.java b/api/src/test/java/edu/cornell/mannlib/vitro/webapp/imageprocessor/imageio/JaiImageProcessorTester2.java new file mode 100644 index 000000000..87e89c147 --- /dev/null +++ b/api/src/test/java/edu/cornell/mannlib/vitro/webapp/imageprocessor/imageio/JaiImageProcessorTester2.java @@ -0,0 +1,272 @@ +/* $This file is distributed under the terms of the license in /doc/license.txt$ */ + +package edu.cornell.mannlib.vitro.webapp.imageprocessor.imageio; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Component; +import java.awt.Frame; +import java.awt.GridLayout; +import java.awt.Label; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.awt.image.Raster; +import java.io.FileInputStream; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import javax.media.jai.JAI; +import javax.media.jai.RenderedOp; +import javax.media.jai.operator.StreamDescriptor; +import javax.swing.BorderFactory; +import javax.swing.JPanel; + +import edu.cornell.mannlib.vitro.webapp.imageprocessor.jai.JaiImageProcessor; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.log4j.Appender; +import org.apache.log4j.Level; +import org.apache.log4j.Logger; +import org.apache.log4j.PatternLayout; + +import com.sun.media.jai.codec.MemoryCacheSeekableStream; + +import edu.cornell.mannlib.vitro.webapp.imageprocessor.imageio.JaiImageProcessorTester2.CropDataSet.CropData; +import edu.cornell.mannlib.vitro.webapp.imageprocessor.jai.JaiImageProcessor.NonNoisyImagingListener; +import edu.cornell.mannlib.vitro.webapp.modules.imageProcessor.ImageProcessor.CropRectangle; +import edu.cornell.mannlib.vitro.webapp.modules.imageProcessor.ImageProcessor.Dimensions; + +/** + * This is not a unit test, so it is not named BlahBlahTest. + * + * Instead, it's a test harness that creates thumbnails and displays them in a + * window on the screen. It takes human intervention to evaluate. + * + * The goal here is to see whether differences in crop dimensions might cause + * one or more black edges on the thumbnails. + */ +@SuppressWarnings("deprecation") +public class JaiImageProcessorTester2 extends Frame { + private static final Log log = LogFactory + .getLog(JaiImageProcessorTester2.class); + + private static final int ROWS = 6; + private static final int COLUMNS = 9; + + private static final int EDGE_THRESHOLD = 6000; + + private static final Dimensions THUMBNAIL_SIZE = new Dimensions(200, 200); + + /** Keep things quiet. */ + static { + JAI.getDefaultInstance().setImagingListener( + new NonNoisyImagingListener()); + } + + private final String imagePath; + private final JaiImageProcessor thumbnailer; + + public JaiImageProcessorTester2(String imagePath, + CropDataSet cropDataSet) { + this.imagePath = imagePath; + this.thumbnailer = new JaiImageProcessor(); + + setTitle("Cropping edging test"); + addWindowListener(new CloseWindowListener()); + setLayout(new GridLayout(ROWS, COLUMNS)); + + for (CropData cropData : cropDataSet.crops()) { + add(createImagePanel(cropData)); + } + + pack(); + setVisible(true); + } + + private Component createImagePanel(CropData cropData) { + RenderedOp image = createCroppedImage(cropData); + + + Set blackSides = checkBlackEdges(image); + if (!blackSides.isEmpty()) { + log.warn("edges at " + cropData + ", " + blackSides); + } + + String legend = "left=" + cropData.left + ", top=" + cropData.top + + ", size=" + cropData.size; + Label l = new Label(); + l.setAlignment(Label.CENTER); + if (!blackSides.isEmpty()) { + l.setBackground(new Color(0xFFDDDD)); + legend += " " + blackSides; + } + l.setText(legend); + + JPanel p = new JPanel(); + p.setLayout(new BorderLayout()); + p.add("South", l); + p.add("Center", new javax.media.jai.widget.ImageCanvas(image)); + p.setBackground(new Color(0xFFFFFF)); + p.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); + + return p; + } + + private RenderedOp createCroppedImage(CropData cropData) { + try { + InputStream mainStream = new FileInputStream(imagePath); + CropRectangle rectangle = new CropRectangle(cropData.left, + cropData.top, cropData.size, cropData.size); + InputStream thumbnailStream = thumbnailer.cropAndScale(mainStream, + rectangle, THUMBNAIL_SIZE); + + return StreamDescriptor.create(new MemoryCacheSeekableStream( + thumbnailStream), null, null); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private Set checkBlackEdges(RenderedOp image) { + Raster imageData = image.getData(); + + int minX = imageData.getMinX(); + int minY = imageData.getMinY(); + int maxX = minX + imageData.getWidth() - 1; + int maxY = minY + imageData.getHeight() - 1; + + Set blackSides = new HashSet(); + if (isBlackEdge(minX, minX, minY, maxY, imageData)) { + blackSides.add("left"); + } + if (isBlackEdge(minX, maxX, minY, minY, imageData)) { + blackSides.add("top"); + } + if (isBlackEdge(maxX, maxX, minY, maxY, imageData)) { + blackSides.add("right"); + } + if (isBlackEdge(minX, maxX, maxY, maxY, imageData)) { + blackSides.add("bottom"); + } + return blackSides; + } + + private boolean isBlackEdge(int fromX, int toX, int fromY, int toY, + Raster imageData) { + int edgeTotal = 0; + try { + for (int col = fromX; col <= toX; col++) { + for (int row = fromY; row <= toY; row++) { + edgeTotal += sumPixel(imageData, col, row); + } + } + } catch (Exception e) { + log.error("can't sum edge: fromX=" + fromX + ", toX=" + toX + + ", fromY=" + fromY + ", toY=" + toY + ", imageWidth=" + + imageData.getWidth() + ", imageHeight=" + + imageData.getHeight() + ": " + e); + } + + log.debug("edge total = " + edgeTotal); + return edgeTotal < EDGE_THRESHOLD; + } + + private int sumPixel(Raster imageData, int col, int row) { + int pixelSum = 0; + int[] pixel = imageData.getPixel(col, row, new int[0]); + for (int value : pixel) { + pixelSum += value; + } + return pixelSum; + } + + /** + *
+	 * The plan:
+	 * 
+	 * Provide the path to an image file.
+	 * Figure how many images can fit on the screen.
+	 * Crop in increments, starting at 0,0 and varying the size of the crop.
+	 * Crop in increments, incrementing from 0,0 downward, and varying the size of the crop.
+	 * 
+	 * Start by creating 4 x 4 images in a window, and incrementing from 201 to 216.
+	 * 
+ */ + + public static void main(String[] args) { + Logger rootLogger = Logger.getRootLogger(); + Appender appender = (Appender) rootLogger.getAllAppenders() + .nextElement(); + appender.setLayout(new PatternLayout("%-5p [%c{1}] %m%n")); + + Logger.getLogger(JaiImageProcessor.class).setLevel(Level.DEBUG); + Logger.getLogger(JaiImageProcessorTester2.class).setLevel( + Level.INFO); + + CropDataSet cropDataSet = new CropDataSet(); + for (int i = 0; i < ROWS * COLUMNS; i++) { +// cropDataSet.add(i, i, 201 + i); + cropDataSet.add(0, 0, 201 + i); + } + + new JaiImageProcessorTester2( + "C:/Users/jeb228/Pictures/wheel.png", cropDataSet); + +// new ImageUploaderThumbnailerTester_2( +// "C:/Users/jeb228/Pictures/DSC04203w-trans.jpg", cropDataSet); + + // new ImageUploaderThumbnailerTester_2( +// "C:/Development/JIRA issues/NIHVIVO-2477 Black borders on thumbnails/" +// + "images from Alex/uploads/file_storage_root/a~n/411/9/" +// + "De^20Bartolome^2c^20Charles^20A^20M_100037581.jpg", +// cropDataSet); + } + + // ---------------------------------------------------------------------- + // helper classes + // ---------------------------------------------------------------------- + + private class CloseWindowListener extends WindowAdapter { + @Override + public void windowClosing(WindowEvent e) { + setVisible(false); + dispose(); + System.exit(0); + } + } + + public static class CropDataSet { + private final List crops = new ArrayList(); + + CropDataSet add(int left, int top, int size) { + crops.add(new CropData(left, top, size)); + return this; + } + + Collection crops() { + return Collections.unmodifiableCollection(crops); + } + + public static class CropData { + final int left; + final int top; + final int size; + + CropData(int left, int top, int size) { + this.left = left; + this.top = top; + this.size = size; + } + + @Override + public String toString() { + return "CropData[" + left + ", " + top + ", " + size + "]"; + } + } + } +}