Image processing with the atkFileAttribute

From Achievo/ATK Wiki

Jump to: navigation, search

ATK Howto: Image processing with the atkFileAttribute

Complexity: advanced
Author: Ivo Jansch <ivo@achievo.org>

List of other Howto's

Contents

Intro

The atkFileAttribute can be used to upload images and other files into an ATK application. Sometimes however you will want to process files before they are uploaded.

In this howto we create a custom atkFileAttribute to handle this. As an example, we will do three things. First, we will make a thumbnail version of the uploaded image, and second, we make this thumbnail look Sepia, and finally, we want to add a small border around the generated thumbnail.

Requirements

All you need is to set up a node that contains at least an atkFileAttribute. Make sure you have a working sample before you start with this howto.

Your atkFileAttribute code may look something like the following:

  $this->add(new atkFileAttribute("picture", atkconfig("atkroot")."pictures/"))

This example uses a field called 'picture' in the database to hold the file name, and the image itself will be uploaded to the pictures/ subdirectory of our application.

Creating a custom attribute

First, we create a custom attribute that derives from atkFileAttribute. If you need this attribute only once, you can place the code directly in the class.<yournode>.inc file of the node you want to use it in, but, assuming you want to reuse this attribute later, we store it as a separate attribute:

  [ivo@nikita workspace]$ cd <yourapplication>/modules/<yourmodule>

You can in fact use any module you have; if you have a lot of custom attributes, you might want to consider creating an 'attriblib' module (as in 'library of attributes') or something along those lines to hold all the attributes that you create. For the rest of this howto, I'll assume that your application is called 'app' and your module is called 'attriblib'

After you decided what module to place the attribute in, create a subdirectory 'attributes':

  [ivo@nikita yourmodule]$ mkdir attributes/

Now it's time to decide a name for the new attribute. For our sepia example, let's call it the 'sepiaAttribute'. Create a file called 'class.sepiaattribute.inc' in the attributes/ subdirectory with the following contents:

  <?php
    // Include the base class
    useattrib("atkfileattribute");
  
    class sepiaAttribute extends atkFileAttribute
    {
    }
  ?>

You now have a custom attribute which behaves identical to the atkFileAttribute, since we have not added any processing yet. But we can already add this attribute to our node.

Adding the attribute to the node

Open your node class, and add the following useattrib call:

  useattrib("attriblib.sepiaattribute");

As you can see, you can use the modulename and the name of the attribute in the useattrib call. ATK will derive the actual location of the file from this identifier.

In your node, replace the atkFileAttribute with the sepiaAttribute like this:

  $this->add(new sepiaAttribute("picture", atkconfig("atkroot")."pictures/"))

When you now browse your application, everything should appear unchanged. You've added a custom attribute, but it doesn't do anything custom yet, so it acts just like a regular atkFileAttribute.

Adding image processing

The atkFileAttribute contains a method called 'processFile'. By default this method does not do anything, but it can be implemented in custom attributes to do the processing.

To make the image Sepia, I googled a bit and found a useful 'class.image.php' on http://php.amnuts.com/index.php?do=view&id=16 (there's no reason to reinvent this wheel, right?). Let's download it and place it in the attriblib/ directory. According to the docs, all we need to do is call Image::sepia($img) and it converts the image to sepia. Below is the code that does all the image conversion. I'll let the comments speak for itself:

  class sepiaAttribute extends atkFileAttribute
  {
    // By overriding this method, we can do processing on the uploaded image.
    // The path and filename of the image are passed as parameters.
    function processFile($path, $filename)
    {
      // Include the image class we downloaded. By using the 'moduleDir' method, we
      // ensure that our module works regardless of where it is installed.
      include_once(moduleDir("attriblib")."class.image.php");
  
       // Read the image as a PHP image resource, and convert it to sepia:
       $img = imagecreatefrompng($path.$filename);
       Image::sepia($img);
  
       // Now we create a thumbnail with a fixed size.
       $img2 = imagecreate(220,120);
  
       // We've made the image a bit larger than what we're going to use 
       // for the thumbnail, so the remaining space will be used as 
       // backgroundcolor. By allocating the color, we create the background.
       // Let's choose 'read' for our border.
       $background_color = imagecolorallocate($img2, 255, 0, 0);
  
       // To resize the image, we need to know its original size, for which we
       // can use the 'getimagesize' function from PHP.
       list($width, $height, $type, $attr) = getimagesize($path.$filename);
  
       // Now create the thumbnail.
       imagecopyresized($img2, $img, 10, 10, 0, 0, 200, 100, $width, $height);
  
       // Make the thumbname file have the same name as the original, with prefix
       // "thumb_", and write it to the same directory as the original image.
       $thumb = "thumb_".$filename;
       imagepng($img2, $path.$thumb);
    }
  }

You may notice that this attribute now only works with PNG files, because we used imagecreatefrompng and imagepng. This however is just for the sake of example. You could check the extension and make it work for other image formats easily.

Now we have an attribute that generates a thumbnail with some effects. If you now give it a try you will notice that the thumbnail image is being generated when you upload a file; however, ATK still displays the larger original image. To remedy this, we have to tell the new attribute to use the thumbnail instead of the original image. This can be done by overriding the display() method in our custom attribute. Have a look at the following code sample (I left out the processFile method contents for brevity):

  class sepiaAttribute extends atkFileAttribute
  {
    function processFile($path, $filename)
    {
      (...)
    }
  
    function display($record, $mode="")
    {
      // In recodlists, show the thumbnail instead of the larger image.
      if ($mode=="list")
      {
        // use the thumb_... version of the image
        return '<img src="'.$this->m_url.
                "thumb_".
                $record[$this->fieldName()]["orgfilename"].'">';
      }
      return parent::display($record, $mode);
    }
  }

You see it takes a little bit of knowledge about the original attribute. The m_url member variable contains the location of the uploaded file, and the 'orgfilename' part in the $record is the filename of the image that is used in the original display method. If you derive an attribute from another attribute, have a look at the original source to find out how the attribute works.

In the above example, you see that we use the parent::display logic to handle the actual image display when not in list mode. This way, we don't have to reimplement this behaviour in our derived class.

After adding this code, check the application in your browser. You'll now see that when you upload new images, the recordlist will show the manipulated thumbnail image. When you click on to the edit or view page of a record, you will see the larger original image.

Personal tools
Navigation