package AngioTool; import Algorithms.PreprocessColor; import Pixels.ArgbBuffer; import Pixels.Canvas; import Pixels.ImageFile; import Utils.FloatBufferPool; import Utils.ISliceRunner; import Utils.Misc; import Utils.RefVector; import Xlsx.*; import java.awt.Color; import java.awt.Component; import java.awt.Dimension; import java.awt.Font; import java.awt.FontMetrics; import java.awt.Graphics; import java.awt.Rectangle; import java.awt.Toolkit; import java.awt.event.*; import java.awt.geom.AffineTransform; import java.awt.image.BufferedImage; import java.awt.image.DataBufferInt; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.concurrent.TimeUnit; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.LinkedBlockingQueue; import javax.swing.*; import javax.swing.filechooser.FileFilter; public class ImagingWindow extends JFrame implements ActionListener, KeyListener { public static class ImagingDisplay extends JPanel implements MouseMotionListener, MouseWheelListener { public static final double ZOOM_BASE = 1.2; boolean waiting = false; boolean isTimerActive = false; AnalyzerParameters pendingParams = null; ArgbBuffer source; int[] rowScratch; float[] blurWnd; float[] luminanceTable; int imgWidth; int imgHeight; BufferedImage drawingImage; Color backgroundColor; Color overlayBackColor; Color[] wheelColors; int loadTicks; Rectangle areaRect; AnalyzerParameters currentParams; Analyzer.Stats currentStats; RefVector statsStrings; int statsWidth; int statsLineHeight; boolean shouldShowStats; int zoomLevels; boolean wasDragging; int panStartX; int panStartY; int panX; int panY; double imageOriginX; double imageOriginY; final Timer loadingTimer = new Timer(100, new ActionListener() { @Override public void actionPerformed(ActionEvent evt) { if (!waiting) { isTimerActive = false; loadingTimer.stop(); } else { isTimerActive = true; loadTicks++; repaint(); } } }); public ImagingDisplay(ArgbBuffer source, boolean initiallyShowStats) { this.source = source; this.imgWidth = source.width; this.imgHeight = source.height; this.rowScratch = new int[Math.max(imgWidth, imgHeight)]; this.blurWnd = new float[25 * 3]; this.luminanceTable = new float[255]; this.drawingImage = new BufferedImage(imgWidth, imgHeight, BufferedImage.TYPE_INT_RGB); this.backgroundColor = new Color(0); this.overlayBackColor = new Color(32, 48, 128, 192); this.wheelColors = new Color[8]; this.loadTicks = 0; this.areaRect = new Rectangle(); this.currentParams = null; this.currentStats = null; this.statsStrings = new RefVector<>(String.class); this.statsWidth = 0; this.shouldShowStats = initiallyShowStats; this.zoomLevels = 0; this.wasDragging = false; this.panStartX = 0; this.panStartY = 0; this.panX = 0; this.panY = 0; this.imageOriginX = 0.5 * imgWidth; this.imageOriginY = 0.5 * imgHeight; final int dull = 64; final int factor = (256 - dull) / (wheelColors.length - 1); for (int i = 0; i < wheelColors.length; i++) { int lum = dull + (wheelColors.length - i - 1) * factor; wheelColors[i] = new Color(lum, lum, lum); } this.addMouseMotionListener(this); this.addMouseWheelListener(this); } public void setNewImage(ArgbBuffer newImage) { if (source != null) ImageFile.releaseImage(source); source = newImage; imgWidth = source.width; imgHeight = source.height; rowScratch = new int[Math.max(imgWidth, imgHeight)]; drawingImage = new BufferedImage(imgWidth, imgHeight, BufferedImage.TYPE_INT_RGB); imageOriginX = 0.5 * imgWidth; imageOriginY = 0.5 * imgHeight; } public static double getZoomFactor(int zoomIndex) { double rounding = (1 << (Math.min(Math.max(-zoomIndex + 2, 2), 16))); return Math.floor(Math.pow(ZOOM_BASE, zoomIndex) * rounding + 0.5) / rounding; } @Override public Dimension getPreferredSize() { return new Dimension(imgWidth, imgHeight); } @Override public void paintComponent(Graphics g) { g.getClipBounds(areaRect); if (areaRect.width <= 0 || areaRect.height <= 0 || imgWidth <= 0 || imgHeight <= 0) { super.paintComponent(g); return; } if (!waiting || !isTimerActive) { double zoomFactor = getZoomFactor(this.zoomLevels); double wRatio = (double)areaRect.width / (double)this.imgWidth; double hRatio = (double)areaRect.height / (double)this.imgHeight; double scaleFactor = 1.0 / (zoomFactor * Math.min(wRatio, hRatio)); int dPanX = this.panStartX - this.panX; if (dPanX != 0) this.imageOriginX += dPanX * scaleFactor; int dPanY = this.panStartY - this.panY; if (dPanY != 0) this.imageOriginY += dPanY * scaleFactor; this.panStartX = this.panX; this.panStartY = this.panY; int srcX = (int)(this.imageOriginX - areaRect.width * 0.5 * scaleFactor + 0.5); int srcY = (int)(this.imageOriginY - areaRect.height * 0.5 * scaleFactor + 0.5); int srcW = (int)(areaRect.width * scaleFactor + 0.5); int srcH = (int)(areaRect.height * scaleFactor + 0.5); int canvasX1 = Math.max((int)(-srcX / scaleFactor + 0.5), 0); int canvasY1 = Math.max((int)(-srcY / scaleFactor + 0.5), 0); int canvasX2 = Math.min((int)((this.imgWidth - srcX) / scaleFactor + 0.5), areaRect.width); int canvasY2 = Math.min((int)((this.imgHeight - srcY) / scaleFactor + 0.5), areaRect.height); g.drawImage(drawingImage, canvasX1, canvasY1, canvasX2, canvasY2, Math.max(srcX, 0), Math.max(srcY, 0), Math.min(srcX + srcW, this.imgWidth), Math.min(srcY + srcH, this.imgHeight), backgroundColor, null ); g.setColor(backgroundColor); if (canvasX1 > 0) g.fillRect(0, canvasY1, canvasX1, canvasY2 - canvasY1); if (canvasX2 < areaRect.width) g.fillRect(canvasX2, canvasY1, areaRect.width - canvasX2, canvasY2 - canvasY1); if (canvasY1 > 0) g.fillRect(0, 0, areaRect.width, canvasY1); if (canvasY2 < areaRect.height) g.fillRect(0, canvasY2, areaRect.width, areaRect.height - canvasY2); } if (waiting) { int centerX = areaRect.width / 2; int centerY = areaRect.height / 2; for (int i = 0; i < wheelColors.length; i++) { double angle = 2.0 * Math.PI * i / wheelColors.length; int x = (int)(20.0 * Math.cos(angle) + 0.5); int y = (int)(20.0 * Math.sin(angle) + 0.5); g.setColor(wheelColors[(loadTicks + i) % wheelColors.length]); g.fillOval(centerX + x - 6, centerY + y - 6, 13, 13); } } else if (shouldShowStats && this.currentParams != null && this.currentStats != null) { if (this.statsStrings.size == 0) { double linearScaleFactor = currentParams.linearScalingFactor > 0.0 ? currentParams.linearScalingFactor : 1.0; double areaScaleFactor = linearScaleFactor * linearScaleFactor; double imageArea = currentStats.imageWidth * currentStats.imageHeight * areaScaleFactor; double allantoisPercentage = currentStats.allantoisMMArea * 100.0 / imageArea; double junctionsAreaPercentage = 100.0 * currentStats.junctionsPerScaledArea; statsStrings.add( "Width x Height: " + currentStats.imageWidth + " x " + currentStats.imageHeight ); statsStrings.add( "Explant Area, %: " + Misc.formatDouble(currentStats.allantoisMMArea, 2) + ", " + Misc.formatDouble(allantoisPercentage, 3) + "%" ); statsStrings.add( "Vessels Area, %: " + Misc.formatDouble(currentStats.vesselMMArea, 2) + ", " + Misc.formatDouble(currentStats.vesselPercentageArea, 3) + "%" ); statsStrings.add( "Total Junctions: " + Misc.formatDouble(currentStats.totalNJunctions) ); statsStrings.add( "Junctions Density %: " + Misc.formatDouble(junctionsAreaPercentage, 5) + "%" ); statsStrings.add( "Vessels Length: Total, Average: " + Misc.formatDouble(currentStats.totalLength, 3) + ", " + Misc.formatDouble(currentStats.averageBranchLength, 3) ); statsStrings.add( "End Points: " + currentStats.totalNEndPoints ); if (currentParams.shouldComputeThickness) { statsStrings.add( "Average Vessel Diameter: " + Misc.formatDouble(currentStats.averageVesselDiameter, 3) ); } if (currentParams.shouldComputeLacunarity) { statsStrings.add( "E Lacunarity: Medial, Mean, Gradient: " + Misc.formatDouble(currentStats.ELacunarityMedial, 4) + ", " + Misc.formatDouble(currentStats.meanEl, 4) + ", " + Misc.formatDouble(currentStats.ELacunarityCurve, 4) ); statsStrings.add( "F Lacunarity: Medial, Mean, Gradient: " + Misc.formatDouble(currentStats.FLacunarityMedial, 4) + ", " + Misc.formatDouble(currentStats.meanFl, 4) + ", " + Misc.formatDouble(currentStats.FLacunarityCurve, 4) ); } FontMetrics metrics = g.getFontMetrics(); int widestWidth = 0; for (int i = 0; i < statsStrings.size; i++) widestWidth = Math.max(widestWidth, metrics.stringWidth(statsStrings.buf[i])); this.statsWidth = widestWidth; this.statsLineHeight = metrics.getHeight(); } g.setColor(overlayBackColor); g.fillRect(4, 4, this.statsWidth + 16, this.statsLineHeight * this.statsStrings.size + 16); g.setColor(Color.WHITE); for (int i = 0; i < this.statsStrings.size; i++) g.drawString(statsStrings.buf[i], 12, (i+1) * this.statsLineHeight + 8); } } @Override public void mouseDragged(MouseEvent evt) { if (waiting) return; int x = evt.getX(); int y = evt.getY(); if (x != this.panX || y != this.panY) { if (!wasDragging) { this.panStartX = x; this.panStartY = y; } this.panX = x; this.panY = y; this.wasDragging = true; this.repaint(); } } @Override public void mouseMoved(MouseEvent evt) { this.wasDragging = false; } @Override public void mouseWheelMoved(MouseWheelEvent evt) { if (waiting) return; int clicks = evt.getWheelRotation(); if (clicks != 0) zoomBy(-clicks, evt); } public void zoomBy(int levels, MouseWheelEvent evt) { int prevZoom = this.zoomLevels; this.zoomLevels = Math.min(Math.max(this.zoomLevels + levels, -16), 32); if (evt != null) { int x1 = evt.getX(); int y1 = evt.getY(); int halfW = this.areaRect.width / 2; int halfH = this.areaRect.height / 2; int dx = x1 - halfW; int dy = y1 - halfH; double dz = getZoomFactor(this.zoomLevels) / getZoomFactor(prevZoom); int x2 = halfW + (int)(dx * dz + 0.5); int y2 = halfH + (int)(dy * dz + 0.5); this.panStartX = x2; this.panStartY = y2; this.panX = x1; this.panY = y1; } this.repaint(); } public void panBy(int xUnits, int yUnits) { int size = Math.max(this.areaRect.width, this.areaRect.height) / 8; this.panX = this.panStartX - xUnits * size; this.panY = this.panStartY - yUnits * size; this.repaint(); } public void toggleStats(boolean enabled) { this.shouldShowStats = enabled; this.repaint(); } public int[] getDrawingBuffer() { return ((DataBufferInt)drawingImage.getRaster().getDataBuffer()).getData(); } public void notifyImageProcessing(boolean shouldCopyFromSource) { this.waiting = true; this.currentParams = null; this.currentStats = null; this.statsStrings.size = 0; int[] outPixels = getDrawingBuffer(); Canvas.blurArgbImage( outPixels, shouldCopyFromSource ? source.pixels : outPixels, imgWidth, imgHeight, rowScratch, blurWnd ); loadingTimer.start(); this.repaint(); } public AnalyzerParameters onImageFinished(AnalyzerParameters params, Analyzer.Data data, Analyzer.Stats stats) { this.currentParams = params; this.currentStats = stats; this.statsStrings.size = 0; int[] outPixels = getDrawingBuffer(); if (stats == null) { System.arraycopy(source.pixels, 0, outPixels, 0, imgWidth * imgHeight); this.waiting = false; this.repaint(); return null; } float[] preprocessedImage = null; if (params.shouldRemapColors && params.shouldExpandOutputToGrayScale) { preprocessedImage = FloatBufferPool.acquireAsIs(imgWidth * imgHeight); PreprocessColor.transformToMonoFloatArray( preprocessedImage, source.pixels, imgWidth, imgHeight, (float)params.hueTransformWeight, (float)params.brightnessTransformWeight, params.targetRemapColor.getRGB(), params.voidRemapColor.getRGB(), (float)params.saturationFactor, luminanceTable ); } Analyzer.drawOverlay( params, data.convexHull, data.skelResult, data.analysisImage.buf, outPixels, source.pixels, preprocessedImage, imgWidth, imgHeight, source.brightestChannel ); if (preprocessedImage != null) FloatBufferPool.release(preprocessedImage); if (pendingParams != null) { notifyImageProcessing(false); AnalyzerParameters temp = pendingParams; pendingParams = null; return temp; } this.waiting = false; this.repaint(); return null; } } public static final int MAX_WORKERS = 4; public static final ThreadPoolExecutor threadPool = new ThreadPoolExecutor( /* corePoolSize */ MAX_WORKERS, /* maximumPoolSize */ MAX_WORKERS, /* keepAliveTime */ 30, /* unit */ TimeUnit.SECONDS, /* workQueue */ new LinkedBlockingQueue<>() ); AngioToolGui2 parentFrame; File inputFile; String defaultPath; double prevImageScale; boolean everSaved; ImagingDisplay imageUi; JLabel labelZoom = new JLabel(); JButton btnZoomIn = new JButton(); JButton btnZoomOut = new JButton(); JLabel labelPan = new JLabel(); JButton btnPanLeft = new JButton(); JButton btnPanUp = new JButton(); JButton btnPanRight = new JButton(); JButton btnPanDown = new JButton(); JCheckBox cbShowStats = new JCheckBox(); JLabel labelSaveImage = new JLabel(); JLabel labelImageWasSaved = new JLabel(); JButton btnSaveImage = new JButton(); JTextField textSaveImage = new JTextField(); JLabel labelSaveSpreadsheet = new JLabel(); JLabel labelSpreadsheetWasSaved = new JLabel(); JButton btnSaveSpreadsheet = new JButton(); JTextField textSaveSpreadsheet = new JTextField(); final Analyzer.Data analyzerData = new Analyzer.Data(null, 0); final ISliceRunner sliceRunner = new ISliceRunner.Parallel(Analyzer.threadPool); public ImagingWindow(AngioToolGui2 uiFrame, ArgbBuffer image, double imageScale, File sourceFile, String defaultPath) { super("Analysis - " + sourceFile.getName()); this.parentFrame = uiFrame; this.imageUi = new ImagingDisplay(image, true); this.prevImageScale = imageScale; this.inputFile = sourceFile; this.defaultPath = defaultPath; this.everSaved = false; this.labelZoom.setText("Zoom:"); this.btnZoomIn.setIcon(AngioTool.ATPlus); this.btnZoomOut.setIcon(AngioTool.ATMinus); this.labelPan.setText("Move:"); this.btnPanLeft.setIcon(AngioTool.ATLeft); this.btnPanUp.setIcon(AngioTool.ATUp); this.btnPanDown.setIcon(AngioTool.ATDown); this.btnPanRight.setIcon(AngioTool.ATRight); this.cbShowStats.setText("Display Stats"); this.cbShowStats.setSelected(true); this.labelSaveImage.setText("Save result image"); Misc.setNewFontStyleOn(this.labelImageWasSaved, Font.ITALIC); this.labelImageWasSaved.setHorizontalAlignment(SwingConstants.TRAILING); this.btnSaveImage.setIcon(AngioTool.ATFolderSmall); this.textSaveImage.addKeyListener(this); this.labelSaveSpreadsheet.setText("Save stats to spreadsheet"); Misc.setNewFontStyleOn(this.labelSpreadsheetWasSaved, Font.ITALIC); this.labelSpreadsheetWasSaved.setHorizontalAlignment(SwingConstants.TRAILING); this.btnSaveSpreadsheet.setIcon(AngioTool.ATExcelSmall); this.textSaveSpreadsheet.addKeyListener(this); } public ImagingWindow showDialog() { imageUi.notifyImageProcessing(true); setUiInteractivity(false); dispatchAnalysisTask(parentFrame.buildAnalyzerParamsFromUi()); JPanel dialogPanel = new JPanel(); GroupLayout layout = new GroupLayout(dialogPanel); dialogPanel.setLayout(layout); layout.setAutoCreateGaps(true); layout.setAutoCreateContainerGaps(true); arrangeUi(layout); int nComponents = dialogPanel.getComponentCount(); for (int i = 0; i < nComponents; i++) { Component elem = dialogPanel.getComponent(i); if (elem instanceof AbstractButton) ((AbstractButton)elem).addActionListener(this); } this.addWindowListener(new WindowAdapter() { @Override public void windowClosing(WindowEvent evt) { parentFrame.closeImagingWindow(ImagingWindow.this); } }); this.getContentPane().add(dialogPanel); this.pack(); Dimension preferredSize = this.getPreferredSize(); Dimension screenDim = Toolkit.getDefaultToolkit().getScreenSize(); this.setSize(new Dimension( Math.min(preferredSize.width, (int)(screenDim.width * 0.9)), Math.min(preferredSize.height, (int)(screenDim.height * 0.9)) )); this.setLocation(700, 50); this.setVisible(true); return this; } public void release() { ImageFile.releaseImage(imageUi.source); imageUi.source = null; if (imageUi.drawingImage != null) { imageUi.drawingImage.flush(); imageUi.drawingImage = null; } } public void updateImage(AnalyzerParameters params) { if (imageUi.waiting) { imageUi.pendingParams = params; return; } setUiInteractivity(false); double newResizeFactor = params.shouldResizeImage ? params.resizingFactor : 1.0; if (newResizeFactor == prevImageScale) imageUi.notifyImageProcessing(false); dispatchAnalysisTask(params); } private void arrangeUi(GroupLayout layout) { final int BS = 32; final int HS = 64; layout.setHorizontalGroup(layout.createParallelGroup() .addComponent(imageUi) .addGroup(layout.createSequentialGroup() .addComponent(labelZoom) .addComponent(btnZoomIn, BS, BS, BS) .addComponent(btnZoomOut, BS, BS, BS) .addComponent(labelPan) .addComponent(btnPanLeft, BS, BS, BS) .addComponent(btnPanUp, BS, BS, BS) .addComponent(btnPanDown, BS, BS, BS) .addComponent(btnPanRight, BS, BS, BS) .addGap(0, 0, Short.MAX_VALUE) .addComponent(cbShowStats) ) .addGroup(layout.createSequentialGroup() .addGroup(layout.createParallelGroup() .addGroup(layout.createSequentialGroup() .addComponent(labelSaveImage) .addGap(0, 0, Short.MAX_VALUE) .addComponent(labelImageWasSaved, HS, HS, HS) ) .addGroup(layout.createSequentialGroup() .addComponent(btnSaveImage) .addComponent(textSaveImage, 0, 0, Short.MAX_VALUE) ) ) .addGap(20) .addGroup(layout.createParallelGroup() .addGroup(layout.createSequentialGroup() .addComponent(labelSaveSpreadsheet) .addGap(0, 0, Short.MAX_VALUE) .addComponent(labelSpreadsheetWasSaved, HS, HS, HS) ) .addGroup(layout.createSequentialGroup() .addComponent(btnSaveSpreadsheet) .addComponent(textSaveSpreadsheet, 0, 0, Short.MAX_VALUE) ) ) ) ); layout.setVerticalGroup(layout.createSequentialGroup() .addComponent(imageUi) .addGroup(layout.createParallelGroup(GroupLayout.Alignment.CENTER) .addComponent(labelZoom) .addComponent(btnZoomIn, BS, BS, BS) .addComponent(btnZoomOut, BS, BS, BS) .addComponent(labelPan) .addComponent(btnPanLeft, BS, BS, BS) .addComponent(btnPanUp, BS, BS, BS) .addComponent(btnPanDown, BS, BS, BS) .addComponent(btnPanRight, BS, BS, BS) .addComponent(cbShowStats) ) .addGap(12) .addGroup(layout.createParallelGroup(GroupLayout.Alignment.BASELINE) .addComponent(labelSaveImage) .addComponent(labelImageWasSaved) .addComponent(labelSaveSpreadsheet) .addComponent(labelSpreadsheetWasSaved) ) .addGroup(layout.createParallelGroup(GroupLayout.Alignment.CENTER) .addComponent(btnSaveImage, BS, BS, BS) .addComponent(textSaveImage, BS, BS, BS) .addComponent(btnSaveSpreadsheet, BS, BS, BS) .addComponent(textSaveSpreadsheet, BS, BS, BS) ) ); } void dispatchAnalysisTask(AnalyzerParameters params) { double newResizeFactor = params.shouldResizeImage ? params.resizingFactor : 1.0; boolean needsReload = newResizeFactor != prevImageScale; if (needsReload) { release(); ArgbBuffer newImage = null; try { newImage = ImageFile.acquireImageForAnalysis(inputFile.getAbsolutePath(), newResizeFactor); if (newImage == null) throw new IOException("Failed to load image for analysis"); imageUi.setNewImage(newImage); } catch (Exception ex) { if (newImage != null) ImageFile.releaseImage(newImage); Misc.showExceptionInDialogBox(ex); setUiInteractivity(true); return; } imageUi.notifyImageProcessing(true); } prevImageScale = newResizeFactor; if (params.shouldRemapColors) PreprocessColor.computeBrightnessTable( imageUi.luminanceTable, params.brightnessLineSegments, params.brightnessLineSegments.length / 2 ); ImagingWindow.threadPool.submit(() -> { try { analyzerData.restart(); if (params.shouldRemapColors) analyzerData.updateBrightnessTable(params.brightnessLineSegments, params.brightnessLineSegments.length / 2); Analyzer.Stats stats = Analyzer.analyze( analyzerData, inputFile, imageUi.source, params, sliceRunner ); SwingUtilities.invokeLater(() -> { AnalyzerParameters nextParams = imageUi.onImageFinished(params, analyzerData, stats); if (nextParams != null) { dispatchAnalysisTask(nextParams); } else { analyzerData.nullify(); setUiInteractivity(true); } }); } catch (Throwable t) { final Throwable error = t; SwingUtilities.invokeLater(() -> { imageUi.onImageFinished(params, analyzerData, null); Misc.showExceptionInDialogBox(error); }); } }); } void setUiInteractivity(boolean enabled) { btnZoomIn.setEnabled(enabled); btnZoomOut.setEnabled(enabled); btnPanLeft.setEnabled(enabled); btnPanUp.setEnabled(enabled); btnPanRight.setEnabled(enabled); btnPanDown.setEnabled(enabled); cbShowStats.setEnabled(enabled); btnSaveImage.setEnabled(enabled); textSaveImage.setEnabled(enabled); btnSaveSpreadsheet.setEnabled(enabled); textSaveSpreadsheet.setEnabled(enabled); labelImageWasSaved.setText(""); labelSpreadsheetWasSaved.setText(""); } void saveResultImage() { String filePath = textSaveImage.getText(); int extIdx = -1; while (true) { if (filePath == null || filePath.length() == 0) { JFileChooser fc = Misc.createFileChooser(); fc.setDialogTitle("Save Result Image"); fc.setDialogType(JFileChooser.SAVE_DIALOG); fc.setCurrentDirectory(new File(defaultPath)); Misc.addImageFileFilters(fc); if (fc.showSaveDialog(this) != 0) return; filePath = fc.getSelectedFile().getAbsolutePath(); FileFilter filter = fc.getFileFilter(); if (filter instanceof SimpleFileFilter) filePath = ((SimpleFileFilter)filter).tailorFileName(filePath); } extIdx = filePath.lastIndexOf('.'); if (extIdx <= 0 || extIdx < Math.max(filePath.lastIndexOf('/'), filePath.lastIndexOf('\\'))) Misc.showDialogBox("Save Result Image", "No image format was specified.\nPlease select an image format, eg. jpg"); else break; filePath = null; }; String format = filePath.substring(extIdx + 1).toLowerCase(); try { DataBufferInt imageBuffer = (DataBufferInt)imageUi.drawingImage.getRaster().getDataBuffer(); ImageFile.saveImage(imageUi.drawingImage, imageBuffer, format, filePath); textSaveImage.setText(filePath); labelImageWasSaved.setText("saved"); } catch (Throwable ex) { Misc.showExceptionInDialogBox(ex); } } void saveResultSpreadsheet() { if (imageUi.currentParams == null || imageUi.currentStats == null) return; String existingXlsxFile = textSaveSpreadsheet.getText(); String[] outStrings = new String[2]; ArrayList sheets = Misc.openSpreadsheetForAppending( outStrings, existingXlsxFile, defaultPath, this ); if (sheets == null) return; defaultPath = outStrings[1]; int fileNameOffset = Misc.getFileNameOffset(outStrings[0]); File folder = new File(outStrings[0].substring(0, fileNameOffset)); String sheetName = outStrings[0].substring(fileNameOffset); try { SpreadsheetWriter sw; if (everSaved) { sw = new SpreadsheetWriter(folder, sheetName); sw.addSheets(sheets); sw.currentSheetIdx = Math.max(sw.currentSheetIdx - 1, 0); } else { sw = Analyzer.createWriterWithNewSheet(sheets, folder, sheetName); } Analyzer.writeResultToSheet(sw, imageUi.currentParams, imageUi.currentStats); everSaved = true; textSaveSpreadsheet.setText(outStrings[0]); labelSpreadsheetWasSaved.setText("saved"); } catch (IOException ex) { Misc.showExceptionInDialogBox(ex); } } @Override public void actionPerformed(ActionEvent evt) { Object source = evt.getSource(); if (source == btnZoomIn) imageUi.zoomBy(2, null); else if (source == btnZoomOut) imageUi.zoomBy(-2, null); else if (source == btnPanLeft) imageUi.panBy(-1, 0); else if (source == btnPanUp) imageUi.panBy(0, -1); else if (source == btnPanDown) imageUi.panBy(0, 1); else if (source == btnPanRight) imageUi.panBy(1, 0); else if (source == cbShowStats) imageUi.toggleStats(cbShowStats.isSelected()); else if (source == btnSaveImage) saveResultImage(); else if (source == btnSaveSpreadsheet) saveResultSpreadsheet(); } @Override public void keyPressed(KeyEvent evt) {} @Override public void keyReleased(KeyEvent evt) {} @Override public void keyTyped(KeyEvent evt) { Object source = evt.getSource(); if (source == textSaveImage) labelImageWasSaved.setText(""); else if (source == textSaveSpreadsheet) labelSpreadsheetWasSaved.setText(""); } }