#PublicSchoolSuccess App – The Details

The #PublicSchoolSuccess Image Generator app is located here.

This page is just the code behind it.

If that’s what you’re after, read on.

Credit Where It’s Due

This is all based off of existing code by Shane Chism, namely his Facebook Picture Overlay script.  Shane’s script is designed to do those icon overlays that you see on Facebook every time there’s a new social cause.  It overlays a small transparent .png in the lower-right of an image, and makes sure the whole thing fits in a 200×600 frame.

Shane’s code still comprises >95% of the code in use, so major props to him for making something so easy for me to modify, and for being a mensch who gives it away for free.

The Modifications

The original script works like this:

  1. Resize and crop the source image until it fits in a 200×600 boundary box.
  2. Create a canvas the same size as the source image.
  3. Insert the source image into the canvas.
  4. Overlay the icon in the lower-right corner (a programmable offset is also available).
  5. Convert to JPG, timestamp, and save.

The #PublicSchoolSuccess version works similarly, but with a couple changes in workflow:

  1. Resize and crop the source image until it fits in a 300×450 boundary box.
    • If the image is portrait, set the width to 300 and crop the bottom to make the height 450.
    • If the image is landscape, set the height to 450 and crop the right to make the width 300.
  2. Rotate the source image to match the angle of the frame.
  3. Create a canvas the size of the overlay (which is the size of the finished image).
  4. Place the rotated/resized portrait on the canvas.
  5. Place the overlay on the canvas.
  6. Convert to JPG, timestamp, and save.

The Code

After this project, I am up to all of 8 hours of php programming (if you include time spent scuzzing with the image to get the right angles and locations).  As such, I make no promises about the quality or reliability of this code.  I did my best to emulate Shane’s methodology for the sake of consistency.  Still, I’m sure I flouted plenty of conventions and eschewed plenty of efficiencies.  Meh.

The package comes in 4 parts:

  • uploader.html – what users actually visit
  • uploader.php – uploads the user’s file and triggers processing
  • YearbookPicOverlay.php – does the actual processing
  • success.html – displays the result to the user

I also modified the .html files to make them look better, but since said modifications are entirely dependent on my site’s css, there’s really no point to including them.

uploader.html
<html xmlns="http://www.w3.org/1999/xhtml">

<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title>Sample Uploader Page</title>
</head>

<body>

	<h2>Facebook Tagger</h2>

	<!-- Enctype MUST be multipart/form-data for file upload -->
	<form action="uploader.php" method="post" enctype="multipart/form-data">
    
    	<!-- For ease-of-use sake I like to put error messages on the same page as uploads -->
        <!-- Alternatively, you can code your website to display errors wherever you like -->
    	<?php @print( $fbtOutput ); ?>
    
    	Please upload your picture (JPEG or JPG format):<br />
    	<input type="file" name="picture_upload" />
        
        <br /><br />
        <input type="submit" name="submit" value="Tag me!" />
    
    </form>
	
	<br /><hr />
    <i>For help and further explanations, see the <a href="http://shanechism.com/code/static/16">documentation</a>.</i>

</body>

</html>
success.html
<html xmlns="http://www.w3.org/1999/xhtml">

<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title>Congratulations, you're tagged! (Sample Uploader Success Page)</title>
</head>

<body>

	<h2>Tagging Success!</h2>

	<div style="margin: 0px auto; text-align: center;">
        
        <b>Here's your freshly tagged image:</b><br /><br />
        To save, right click the image and select "Save Image As," "Save Link As," or "Save Picture As." Then, save it to your desktop!<br /><br />
        
        <!-- As we saw in uploader.php, $image is the path to the image beginning at the resources directory. -->
        <!-- In order to show our image I will need to create the first part of the link. In this case it just starts at the domain name. -->
       	<img src="<?php echo( 'http://' . $_SERVER['SERVER_NAME'] . '/path_to_resources_folder/' . $image ); ?>" alt="Facebook Picture" /><br /><br />
        
        <!-- $_SERVER['PHP_SELF'] is a PHP Superglobal basically pointing the link back to this PHP page -->
        Why not <a href="<?php echo( $_SERVER['PHP_SELF'] ); ?>">tag another</a>?
    
    </div>
    
    <br /><hr />
    <i>For help and further explanations, see the <a href="http://shanechism.com/code/static/16">documentation</a>.</i>

</body>

</html>
uploader.php
<?php

# Check to see if the form has been submitted or not:
if( !isset( $_POST['submit'] ) ){
 
 # Form has not been submitted, show our uploader form and stop the script
 require_once( "uploader.html" );
 exit();
 
}else{
 
 # Form has been submitted, begin processing data
 
 # Include the function file then call it with the uploaded picture:
 # TIP: The "../../ portion is a relative path. You will need to change this
 # path to fit your website's directory structure.
 require_once( 'YearbookPicOverlay.php' );
 
 # Create the FacebookTagger object using our upload value given to us by uploader.html
 $fbt = new FacebookPicOverlay();
 
 # Let's say we're using this script to do an image overlay. Let's invoke the
 # overlay method, which will then return the image file relative to the resources
 # folder (ex: will return resources/processed/imagename.jpg).
 try {
 $image = $fbt->overlay( $_FILES['picture_upload'] );
 }catch( Exception $e ){
 print( "<b>Oops!</b> " . $e->getMessage() );
 print( "<br /><br /><a href=\"javascript:history.go(-1)\">Please go back and try again</a>" );
 exit();
 }
 
 # This will delete all images created more than two days ago (by default).
 # This is helpful in keeping our processed folder at a reasonable file size.
 $fbt->maintenance();
 
 require_once( "success.html" );
 
} 

# That's all, folks!
?>
YearbookPicOverlay.php
<?php
/*************************************************
 * Facebook Picture Overlay Script
 * Version: 2.1.0
 * Coded by: Shane Chism <http://shanechism.com>
 * Updates: http://shanechism.com/code/static/16
 * Distributed under the GNU Public License
 * Modified by Adam Labay
 * Description & Attribution: http://adamlabay.net
 *************************************************/
 
/** \brief Facebook Picture Overlay Script
 * Allows for image overlay to uploaded files based on Facebook's standards
 * @author Shane Chism <schism@acm.org>
**/

/** \brief #PublicSchoolSuccess Overlay Script
 * Creates #PublicSchoolSuccess image using user photo
 * @author Adam Labay <adam@adamlabay.net>
**/
class FacebookPicOverlay {
 
 // --------------------------------------------------
 // CONFIGURATION SECTION - MANDATORY
 // --------------------------------------------------
 // Modify the values in this section according to
 // your own needs. Pay close attention to your
 // directory structure.
 
 # Path to the directory containing the resources and processed folders:
 var $rootPath = "./";
 
 # Resources folder name:
 # (default: "resources/")
 var $resourcesFolder = "resources/"; 
 
 # Folder you would like the processed images saved to:
 # (default: "resources/processed/")
 var $processedFolder = "resources/processed/";
 
 // YEARBOOK PIC CONFIGURATION
 # These variables now represent the width and height of the portrait rather than those of
 # the finished picture. Also included are angle of rotation and position (upper-left).
 var $fbWidth = 330;
 var $fbHeight = 450;
 var $rotAngle = 12;
 var $offsetx = 551;
 var $offsety = 143;
 
 // ** OVERLAY MODE CONFIGURATION
 
 # Overlay image filename and extension (must be placed in the resources folder):
 # (default: "overlay.png")
 # Image will be resized to the dimensions given for export.
 var $overlay = "yearbook_overlay.png";
 var $exportWidth = 600;
 var $exportHeight = 440;
 
 
 # Throw Exceptions?
 # true = User errors will generate an Exception() error
 # false = User errors will return overlay as false, and save error to $this->error
 var $throwExceptions = true;
 
 // --------------------------------------------------
 
 // --------------------------------------------------
 // CONFIGURATION SECTION - OPTIONAL
 // --------------------------------------------------
 // You can fine tune the tagger to your needs,
 // though these options can remain the same and your
 // tagging should still work.
 
 # Maximum image size allowed for upload (in MB):
 # (default: 20)
 var $maxFileSize = 20;
 
 # Save images at this quality (percentage):
 # (smaller quality has a smaller file size but looks worse)
 # (default: 100)
 var $quality = 100;
 
 # Save images in this file format:
 # (options: "jpg", "jpeg", "JPG", "JPEG")
 # (default: "jpg")
 var $extension = "jpg";
 
 // --------------------------------------------------
 
 var $uploaded, $uploadedInfo, $error;
 
 function __construct(){
 
 $this->checkConfig();
 
 }
 
 private function checkConfig(){
 
 if( substr( $this->resourcesFolder, 1 ) == '/' )
 $this->resourcesFolder = substr( $this->resourcesFolder, 1, ( strlen( $this->resourcesFolder ) - 1 ) );
 if( substr( $this->resourcesFolder, -1 ) != '/' )
 $this->resourcesFolder .= "/";
 
 if( substr( $this->processedFolder, 1 ) == '/' )
 $this->processedFolder = substr( $this->processedFolder, 1, ( strlen( $this->processedFolder ) - 1 ) );
 if( substr( $this->processedFolder, -1 ) != '/' )
 $this->processedFolder .= "/";
 
 if( !file_exists( $this->rootPath . $this->resourcesFolder ) )
 $this->printErr( "The resources folder path you have specified is invalid. Please check it and try again (configuration section: <code>\$rootPath</code> and <code>\$resourcesFolder</code>)." );
 if( !file_exists( $this->rootPath . $this->processedFolder ) )
 $this->printErr( "The processed folder path you have specified is invalid. Please check it and try again (configuration section: <code>\$rootPath</code> and <code>\$processedFolder</code>)." );
 
 $overlay = $this->rootPath . $this->resourcesFolder . $this->overlay;
 if( !file_exists( $overlay ) )
 $this->printErr( "The \"overlay\" image you specified in the configuration section (<code>\$overlay</code>) does not exist. Please correct and try again." );
 
 }
 
 private function printErr( $text ){
 die( "<h3>FBT Error:</h3> " . $text );
 }
 
 private function throwErr( $err ){
 $this->error = $err;
 if( $this->throwExceptions )
 throw new Exception( $err );
 }

 # Places your overlay image on the picture and returns the hyperlink
 public function overlay( $uploaded ){
 
 $this->checkConfig();
 $this->uploaded = $uploaded;
 
 $overlay = $this->rootPath . $this->resourcesFolder . $this->overlay;
 $overlaySize = getimagesize( $overlay );
 
 $canvasWidth = $overlaySize[0];
 $canvasHeight = $overlaySize[1];
 
 if( empty( $this->uploaded ) || $uploaded['size'] < 1 ){
 $this->throwErr( "You have not chosen an image to upload!" );
 return false;
 }
 
 $this->uploadedInfo = getimagesize( $this->uploaded['tmp_name'] );
 
 if( $this->uploaded['size'] > ( $this->maxFileSize * 1000000 ) || filesize( $this->uploaded['tmp_name'] ) > ( $this->maxFileSize * 1000000 ) ){
 $this->throwErr( "The file you have chosen to upload is too big." );
 return false;
 }
 
 if( $this->uploadedInfo['mime'] != "image/jpeg" && $this->uploadedInfo['mime'] != "image/jpg" ){
 $this->throwErr( "The file you have chosen to upload is the wrong file type. Please choose a JPG or JPEG file only." );
 return false;
 }
 
 $portrait = array();
 
 $portrait[0] = $this->uploadedInfo[0]; 
 if( ( $portrait[0] * $this->fbHeight ) / $this->fbWidth > $this->uploadedInfo[1] ){
 $portrait[1] = $this->uploadedInfo[1];
 $portrait[0] = ( $portrait[1] * $this->fbWidth ) / $this->fbHeight;
 }
 else
 $portrait[1] = ( $portrait[0] * $this->fbHeight ) / $this->fbWidth;
 
 $src = imagecreatefromjpeg( $this->uploaded['tmp_name'] );
 $srcscaled = imagecreatetruecolor( $this->fbWidth, $this->fbHeight );
 imagecopyresampled( $srcscaled, $src, 0, 0, 0, 0, $this->fbWidth, $this->fbHeight, $portrait[0], $portrait[1] );
 $tmp = imagerotate( $srcscaled, $this->rotAngle, 0 );
 $tmpsize = array();
 $tmpsize[0] = imagesx( $tmp );
 $tmpsize[1] = imagesy( $tmp );
 
 imagealphablending( $tmp, true );
 $overlayRes = imagecreatefrompng( $overlay );
 
 do{
 $filename = time() . "-processed.jpg";
 $file = $this->rootPath . $this->processedFolder . $filename;
 }while( file_exists( $file ) );
 
 $canvas = imagecreatetruecolor( $canvasWidth, $canvasHeight );
 imagecopy( $canvas, $tmp, $this->offsetx, $this->offsety, 0, 0, $tmpsize[0], $tmpsize[1] );
 imagecopy( $canvas, $overlayRes, 0, 0, 0, 0, $canvasWidth, $canvasHeight);
 $canvasout = imagecreatetruecolor ($this->exportWidth, $this->exportHeight);
 imagecopyresampled( $canvasout, $canvas, 0, 0, 0, 0, $this->exportWidth, $this->exportHeight, $canvasWidth, $canvasHeight);
 imagejpeg( $canvasout, $file, $this->quality );
 
 if( !file_exists( $file ) )
 $file = $this->rootPath . $this->resourcesFolder . $this->oops;
 
 imagedestroy( $src );
 imagedestroy( $srcscaled );
 imagedestroy( $tmp );
 imagedestroy( $overlayRes );
 imagedestroy( $canvas );
 imagedestroy( $canvasout );
 
 return ( $this->processedFolder . $filename );
 
 }
 
 # Deletes all files in the processed folder that were created before $timestamp
 # Defaults to 2 days ago
 public function maintenance( $timestamp = NULL ){
 
 $this->checkConfig();
 
 if( $timestamp == NULL )
 # Defaults to 2 days ago
 $timestamp = strtotime( "-2 days" );
 
 if( $timestamp > time() )
 $this->printErr( "You are trying to perform maintenance on files created in the future. This is beyond the script's abilities, please install a time machine to continue." );
 
 if( $handle = opendir( $this->rootPath . $this->processedFolder ) ){
 
 while( false !== ( $filename = readdir( $handle ) ) ){
 
 if( substr( $filename, ( -1 * ( 1 + strlen( $this->extension ) ) ) ) == ( "." . $this->extension ) ){

 $file = $this->rootPath . $this->processedFolder . $filename;
 
 if( filectime( $file ) < $timestamp )
 @unlink( $file );
 }
 
 }
 
 closedir( $handle );
 
 }else{
 $this->printErr( "Unable to access the processed folder. Check your <code>\$rootPath</code> and <code>\$processedFolder</code> settings in the configuration section." );
 }
 
 }
 
}

?>

Leave a Reply