/* * Everyone is free to use, modify, and redistribute this code as long as they * retain this comment block near the top of this file, add this comment block * to files containing significant amounts of code copied from this file, and * not remove any text from this comment block. * Text can be appended to the bottom of this comment block as a change log and * credit to the contributors. * * This code is provided "as is" without any warranty. The contributors will * not be held liable for any damages resulting from the use of this code. * * Change Log: * * YYYY/MM/DD - Contributor Name * -change1 * -change2 * * 2007/07/31 - Dylan Hoen * -Created this file named. DepthOfFieldCanvas.java * -Created public class DepthOfFieldCanvas extends Canvas */ import java.applet.Applet; import java.awt.*; public class DepthOfFieldCanvas extends Canvas { int pagexmax, pageymax; double xmin, xmax, ymin, ymax, xstep, xscale, yscale; int ntext; Color backgroundColor = null; Color graphColor = null; Color axisColor = null; Color textColor = null; Color focalDistanceColor = null; Color farFocalDistanceColor = null; Color bayerLimitColor = null; Color diffractionLimitColor = null; Color circleOfConfusionLimitColor = null; Color minInFocusLinesPerHeightColor = null; Color horizontalFieldOfViewColor = null; Color verticalFieldOfViewColor = null; double sensorWidth; double sensorHeight; double sensorDiagonal; double focalLengthMultiplier; double aperture; double minAperture; double maxAperture; double xRes; double yRes; double pixelCount; double focalLength; double focalDistance; double nearFocalDistance; double farFocalDistance; double diagonalFOV; double horizontalFov; double verticalFov; double pixelWidth; double pixelHeight; double magnification; double pixelsPerMM; double imageDistance; double macroAperture; boolean bayerLowPassFilter; double bayerLowPassPixelSize; double diffractionDiskSize; double minLinesPerHeight; double minInFocusLinesPerHeight; double minInFocusLinesPerHeightLeftCrossing; double minInFocusLinesPerHeightRightCrossing; TextArea leftTextArea = null; TextArea rightTextArea = null; public DepthOfFieldCanvas() { init(); } public void init () { xmin = -1; xmax = 20; ymin = -.25; ymax = 1; ntext = 0; backgroundColor = Color.BLACK; graphColor = Color.WHITE; axisColor = Color.GRAY; textColor = Color.WHITE; focalDistanceColor = Color.MAGENTA; farFocalDistanceColor = Color.RED; bayerLimitColor = Color.GREEN; diffractionLimitColor = Color.ORANGE; circleOfConfusionLimitColor = Color.CYAN; minInFocusLinesPerHeightColor = Color.YELLOW; horizontalFieldOfViewColor = Color.DARK_GRAY; verticalFieldOfViewColor = Color.DARK_GRAY; setBackground( backgroundColor ); sensorWidth = 18.000 / 1000; sensorHeight = 13.500 / 1000; aperture = 8.0; minAperture = 2.0; maxAperture = 22.0; xRes = 3648; yRes = 2736; bayerLowPassFilter = true; focalLength = 50.0 / 1000; focalDistance = 2.0; nearFocalDistance = 1.0; farFocalDistance = 3.0; minInFocusLinesPerHeight = 864; leftTextArea = new TextArea(); rightTextArea = new TextArea(); calcuateNonInputVariables(); } public void calcuateNonInputVariables() { sensorDiagonal = Math.sqrt( sensorWidth * sensorWidth + sensorHeight * sensorHeight ); focalLengthMultiplier = Math.sqrt( .036 * .036 + .024 * .024 ) / sensorDiagonal; pixelCount = xRes * yRes; pixelWidth = sensorWidth / xRes; pixelHeight = sensorHeight / yRes; magnification = focalLength / ( focalDistance - focalLength ); imageDistance = focalDistance * focalLength / ( focalDistance - focalLength ); pixelsPerMM = ( magnification / pixelHeight ) / 1000; diagonalFOV = ( 180 / Math.PI ) * ( 2 * Math.atan( sensorDiagonal / ( 2 * imageDistance ) ) ); horizontalFov = ( 180 / Math.PI ) * ( 2 * Math.atan( sensorWidth / ( 2 * imageDistance ) ) ); verticalFov = ( 180 / Math.PI ) * ( 2 * Math.atan( sensorHeight / ( 2 * imageDistance ) ) ); macroAperture = aperture * ( imageDistance / focalLength ); bayerLowPassPixelSize = bayerLowPassFilter ? pixelHeight * Math.sqrt( 2 ) : pixelHeight; diffractionDiskSize = 1.22 * 600e-9 * macroAperture; minLinesPerHeight = Math.min( f( nearFocalDistance ), f( farFocalDistance ) ); calculateMinInFocusLinesPerHeightLeftCrossing(); calculateMinInFocusLinesPerHeightRightCrossing(); } public void calculateMinInFocusLinesPerHeightLeftCrossing() { double minDistance = focalLength + ( nearFocalDistance - focalLength ) / 10; double maxDistance = focalDistance; for( int i = 0; i < 30; i++ ) { double guess = ( minDistance + maxDistance ) / 2; if( f( guess ) < minInFocusLinesPerHeight ) { minDistance = guess; } else { maxDistance = guess; } } minInFocusLinesPerHeightLeftCrossing = ( minDistance + maxDistance ) / 2; } public void calculateMinInFocusLinesPerHeightRightCrossing() { double minDistance = focalDistance; double maxDistance = Math.max( focalDistance * 1000, farFocalDistance * 2 ); for( int i = 0; i < 30; i++ ) { double guess = ( minDistance + maxDistance ) / 2; if( f( guess ) >= minInFocusLinesPerHeight ) { minDistance = guess; } else { maxDistance = guess; } } minInFocusLinesPerHeightRightCrossing = ( minDistance + maxDistance ) / 2; } public double circleOfConfusion( double x ) { //double circleOfConfusion = ( imageDistance / macroAperture ) * magnification * ( x - focalDistance ) / x; return ( Math.abs( x - focalDistance ) / x ) * ( ( focalLength * focalLength ) / ( aperture * ( focalDistance - focalLength ) ) ); } public double f( double x ) { return sensorHeight / Math.sqrt( Math.pow( bayerLowPassPixelSize, 2 ) + Math.pow( diffractionDiskSize, 2 ) + Math.pow( circleOfConfusion( x ), 2) ); } public int xToScreen( double x ) { return (int)Math.round( ( x - xmin ) * xscale ); } public int yToScreen( double y ) { return pageymax - (int)Math.round( ( y - ymin ) * yscale ); } public void paint( Graphics page ) { leftTextArea.replaceRange( "", 0, 65535 ); rightTextArea.replaceRange( "", 0, 65535 ); calcuateNonInputVariables(); xmin = Math.max( ( nearFocalDistance + focalLength ) / 2, nearFocalDistance - ( farFocalDistance - nearFocalDistance ) / 2 ); xmax = farFocalDistance + ( farFocalDistance - nearFocalDistance ) / 2; ymin = 0; ymax = yRes; pagexmax = getSize ().width; pageymax = getSize ().height; xstep = xmax / ( pagexmax * 4 ); xscale = pagexmax / ( xmax - xmin ); yscale = pageymax / ( ymax - ymin ); double x, y; int xold, xnew, yold, ynew; ntext = 0; // start field of view lines //horizontalFieldOfViewColor = Color.GRAY; //verticalFieldOfViewColor = Color.GRAY; page.setColor( horizontalFieldOfViewColor ); int fovXStart = xToScreen( 0 ); int fovXEnd = xToScreen( xmax ); int fovYStart = yToScreen( yRes / 2 ); //horizontalFov = ( 180 / Math.PI ) * ( 2 * Math.atan( sensorWidth / ( 2 * imageDistance ) ) ); page.drawLine( fovXStart, fovYStart, fovXEnd, fovYStart + (int)Math.round( ( fovXEnd - fovXStart ) * sensorWidth / ( 2 * imageDistance ) ) ); page.drawLine( fovXStart, fovYStart, fovXEnd, fovYStart - (int)Math.round( ( fovXEnd - fovXStart ) * sensorWidth / ( 2 * imageDistance ) ) ); page.setColor( verticalFieldOfViewColor ); //verticalFov = ( 180 / Math.PI ) * ( 2 * Math.atan( sensorHeight / ( 2 * imageDistance ) ) ); page.drawLine( fovXStart, fovYStart, fovXEnd, fovYStart + (int)Math.round( ( fovXEnd - fovXStart ) * sensorHeight / ( 2 * imageDistance ) ) ); page.drawLine( fovXStart, fovYStart, fovXEnd, fovYStart - (int)Math.round( ( fovXEnd - fovXStart ) * sensorHeight / ( 2 * imageDistance ) ) ); // end field of view lines // start axises page.setColor( axisColor ); drawCross( xToScreen( 0 ), yToScreen( 0 ), page ); page.setColor( textColor ); double xIncriment = findNearestOneTwoFiveTen( ( xmax - xmin ) / 12, true ); double minX = xIncriment * Math.ceil( xmin / xIncriment ); for( x = minX; x < xmax + xIncriment / 2; x += xIncriment ) { page.drawLine( xToScreen( x ), pageymax, xToScreen( x ), pageymax - 20 ); page.drawString( (float)x + " m", xToScreen( x ) + 3, pageymax - 3 ); } double yIncriment = findNearestOneTwoFiveTen( ( ymax - ymin ) / 12, true ); double minY = yIncriment * Math.ceil( ymin / yIncriment ); for( y = minY; y < ymax + yIncriment / 2; y += yIncriment ) { page.drawLine( 0, yToScreen( y ), 40, yToScreen( y ) ); page.drawString( (float)y + " LPH", 3, yToScreen( y ) - 3 ); } // end axises // start other lines page.setColor( bayerLimitColor ); drawHorizontalLine( yToScreen( sensorHeight / bayerLowPassPixelSize ), page ); page.setColor( diffractionLimitColor ); drawHorizontalLine( yToScreen( sensorHeight / diffractionDiskSize ), page ); page.setColor( focalDistanceColor ); drawSmallCross( xToScreen( focalDistance ), yToScreen( f( focalDistance ) ), page ); page.setColor( farFocalDistanceColor ); drawSmallCross( xToScreen( nearFocalDistance ), yToScreen( f( nearFocalDistance ) ), page ); drawSmallCross( xToScreen( farFocalDistance ), yToScreen( f( farFocalDistance ) ), page ); page.setColor( minInFocusLinesPerHeightColor ); if( f( focalDistance ) >= minInFocusLinesPerHeight ) { drawSmallCross( xToScreen( minInFocusLinesPerHeightLeftCrossing ), yToScreen( minInFocusLinesPerHeight ), page ); drawSmallCross( xToScreen( minInFocusLinesPerHeightRightCrossing ), yToScreen( minInFocusLinesPerHeight ), page ); page.drawLine( xToScreen( minInFocusLinesPerHeightLeftCrossing ), yToScreen( minInFocusLinesPerHeight ), xToScreen( minInFocusLinesPerHeightRightCrossing ), yToScreen( minInFocusLinesPerHeight ) ); } else { drawHorizontalLine( yToScreen( minInFocusLinesPerHeight ), page ); } // end other lines // start circleOfConfusion graph page.setColor( circleOfConfusionLimitColor ); x = xmin; y = sensorHeight / circleOfConfusion( x ); xold = xToScreen( x ); yold = yToScreen( y ); // Initial point for( x = xmin; x <= xmax; x += xstep ) { y = sensorHeight / circleOfConfusion( x ); xnew = xToScreen( x ); ynew = yToScreen( y ); page.drawLine( xold, yold, xnew, ynew ); xold = xnew; yold = ynew; } // end circleOfConfusion graph // start function graph page.setColor( graphColor ); x = xmin; y = f( x ); xold = xToScreen( x ); yold = yToScreen( y ); // Initial point for( x = xmin; x <= xmax; x += xstep ) { y = f( x ); xnew = xToScreen( x ); ynew = yToScreen( y ); page.drawLine( xold, yold, xnew, ynew ); xold = xnew; yold = ynew; } // end function graph // start text page.setColor( textColor ); leftTextArea.append( "Focal Resolution\t= " + (float)( f( focalDistance ) ) + " lines per height\n" ); leftTextArea.append( "Near Resolution\t= " + (float)( f( nearFocalDistance ) ) + " lines per height\n" ); leftTextArea.append( "Far Resolution\t= " + (float)( f( farFocalDistance ) ) + " lines per height\n" ); leftTextArea.append( "Bayer Low-Pass Limit\t= " + (float)( sensorHeight / bayerLowPassPixelSize ) + " lines per height\n" ); leftTextArea.append( "Diffraction Limit\t= " + (float)( sensorHeight / diffractionDiskSize ) + " lines per height\n" ); leftTextArea.append( "Pixel Count\t= " + (float)( pixelCount / 1000000 ) + " megapixels\n" ); leftTextArea.append( "Focal Pixel Count\t= " + (float)( f( focalDistance ) * f( focalDistance ) * xRes/ yRes / 1000000 ) + " megapixels\n" ); leftTextArea.append( "Min Pixel Count\t= " + (float)( minLinesPerHeight * minLinesPerHeight * xRes / yRes / 1000000 ) + " megapixels\n" ); leftTextArea.append( "Focal Length\t= " + (float)( focalLength * 1000 ) + " mm\n" ); leftTextArea.append( "Focal Length Multiplier\t= " + (float)( focalLengthMultiplier ) + " X\n" ); leftTextArea.append( "Compensated Focal Length\t= " + (float)( imageDistance * 1000 ) + " mm\n" ); leftTextArea.append( "Compensated Aperture\t= " + (float)( macroAperture ) + "\n" ); rightTextArea.append( "Magnification\t= " + (float)( magnification ) + " X\n" ); rightTextArea.append( "Diagonal FOV\t= " + (float)( diagonalFOV ) + " degrees" + "\n" ); rightTextArea.append( "Horizontal FOV\t= " + (float)( horizontalFov ) + " degrees" + "\n" ); rightTextArea.append( "Vertical FOV\t= " + (float)( verticalFov ) + " degrees" + "\n" ); rightTextArea.append( "Focal Plane Width\t= " + (float)( focalDistance / imageDistance * sensorWidth ) + " m" + "\n" ); rightTextArea.append( "Focal Plane Height\t= " + (float)( focalDistance / imageDistance * sensorHeight ) + " m" + "\n" ); rightTextArea.append( "Focal Lines/mm\t= " + (float)( f( focalDistance ) / sensorHeight / 1000 * magnification ) + "\n" ); rightTextArea.append( "Near Lines/mm\t= " + (float)( f( nearFocalDistance ) / sensorHeight / 1000 * magnification * ( focalDistance / nearFocalDistance ) ) + "\n" ); rightTextArea.append( "Far Lines/mm\t= " + (float)( f( farFocalDistance ) / sensorHeight / 1000 * magnification * ( focalDistance / farFocalDistance ) ) + "\n" ); if( f( focalDistance ) >= minInFocusLinesPerHeight ) { rightTextArea.append( "Start of Field\t= " + (float)( minInFocusLinesPerHeightLeftCrossing ) + " m" + "\n" ); if( minInFocusLinesPerHeightRightCrossing < 500 * focalLength ) { rightTextArea.append( "End of Field\t= " + (float)( minInFocusLinesPerHeightRightCrossing ) + " m" + "\n" ); rightTextArea.append( "Depth of Field\t= " + (float)( minInFocusLinesPerHeightRightCrossing - minInFocusLinesPerHeightLeftCrossing ) + " m" + "\n" ); } else { rightTextArea.append( "End of Field\t= infinity m" + "\n" ); rightTextArea.append( "Depth of Field\t= infinity m" + "\n" ); } } else { rightTextArea.append( "Depth of field\t= 0" + "\n" ); rightTextArea.append( "(The whole range is less than " + (float)( minInFocusLinesPerHeight ) + " lines per height)\n" ); } rightTextArea.append( "Sharpness of Infinity\t= " + (float)( f( focalDistance * 1000000 ) ) + " lines per height\n" ); /* double sensorWidth; double sensorHeight; double sensorDiagonal; double focalLengthMultiplier; double aperture; double minAperture; double maxAperture; double xRes; double yRes; double pixelCount; double focalLength; double focalDistance; double nearFocalDistance; double farFocalDistance; double diagonalFOV; double horizontalFov; double verticalFov; double pixelWidth; double pixelHeight; double magnification; double pixelsPerMM; double imageDistance; double macroAperture; boolean bayerLowPassFilter; double bayerLowPassPixelSize; double diffractionDiskSize; double minLinesPerHeight; double minInFocusLinesPerHeight; double minInFocusLinesPerHeightLeftCrossing; double minInFocusLinesPerHeightRightCrossing; */ // emd text } public void drawCross( int x, int y, Graphics page ) { drawVerticalLine( x, page ); drawHorizontalLine( y, page ); } public void drawSmallCross( int x, int y, Graphics page ) { int length = (int)( Math.round( Math.min( pagexmax, pageymax ) / 4.0 ) ); page.drawLine( x, y, x + length, y ); page.drawLine( x, y, x, y + length ); page.drawLine( x, y, x - length, y ); page.drawLine( x, y, x, y - length ); } public void drawVerticalLine( int x, Graphics page ) { page.drawLine( x, 0, x, pageymax ); } public void drawHorizontalLine( int y, Graphics page ) { page.drawLine( 0, y, pagexmax, y ); } public void print( String s, Graphics page ) { ntext++; page.drawString( s, 5, 15 * ntext ); } /** * Finds the nearest [ 1,2,5 ] * 10 ^ n, where n is an integer. * @param input the input to find the nearest [ 1,2,5 ] * 10 ^ n to. * @param higher if true, find the nearest [ 1,2,5 ] * 10 ^ n that is greater than or equal to input, else find the nearest [ 1,2,5 ] * 10 ^ n that is less than or equal to. * @return the nearest [ 1,2,5 ] * 10 ^ n, where n is an integer. */ public static double findNearestOneTwoFiveTen( double input, boolean higher ) { double result = 1; if( input == 0 ) { return 0; } boolean negative = ( input < 0 ); if( negative ) { input = -input; } double power = Math.log( input ) / Math.log( 10 ); int exponent = (int)Math.floor( power ); power -= exponent; if( higher ) { if( power == 0 ) { result = 1 * Math.pow( 10, exponent ); } else if( power <= Math.log( 2 ) / Math.log( 10 ) ) { result = 2 * Math.pow( 10, exponent ); } else if( power <= Math.log( 5 ) / Math.log( 10 ) ) { result = 5 * Math.pow( 10, exponent ); } else { result = 1 * Math.pow( 10, exponent + 1 ); } } else { if( power < Math.log( 2 ) / Math.log( 10 ) ) { result = 1 * Math.pow( 10, exponent ); } else if( power < Math.log( 5 ) / Math.log( 10 ) ) { result = 2 * Math.pow( 10, exponent ); } else { result = 5 * Math.pow( 10, exponent ); } } if( negative ) { result = -result; } return result; } }