Permalink
Browse files

Completed image guide

  • Loading branch information...
lysender committed May 5, 2011
1 parent ea61864 commit 7f4dfbdb9d7638789a4cb81dd6bf9425328fdd6e
View
@@ -0,0 +1,7 @@
+# Examples
+
+The following are mini applications that uses the [Image] module. They are very straight forward and did not include additional code such as validations and the like. They are designed to be simple and should work out of the box.
+
+* [Uploading image](examples/upload)
+* [Cropping profile images](examples/crop)
+* [Serving images with dynamic dimension](examples/dynamic)
@@ -0,0 +1,141 @@
+# Crop Profile Image
+
+This example is very similar to our previous example and even uses the same upload logics. The only difference is that the uploaded image is cropped to square from the center whose dimension is half the original height of the image.
+
+## Controller
+
+We name our new controller as `Controller_Crop` and accessible through `/crop` URL. Assuming that your project is located in [http://localhost/kohana](http://localhost/kohana), then our crop controller is at [http://localhost/kohana/crop](http://localhost/kohana/crop).
+
+~~~
+<?php defined('SYSPATH') or die('No direct script access.');
+
+class Controller_Crop extends Controller {
+
+ public function action_index()
+ {
+ $view = View::factory('crop/index');
+ $this->response->body($view);
+ }
+
+ public function action_do()
+ {
+ $view = View::factory('crop/do');
+ $error_message = NULL;
+ $filename = NULL;
+
+ if ($this->request->method() == Request::POST)
+ {
+ if (isset($_FILES['avatar']))
+ {
+ $filename = $this->_save_image($_FILES['avatar']);
+ }
+ }
+
+ if ( ! $filename)
+ {
+ $error_message = 'There was a problem while uploading the image.
+ Make sure it is uploaded and must be JPG/PNG/GIF file.';
+ }
+
+ $view->uploaded_file = $filename;
+ $view->error_message = $error_message;
+ $this->response->body($view);
+ }
+
+ protected function _save_image($image)
+ {
+ if (
+ ! Upload::valid($image) OR
+ ! Upload::not_empty($image) OR
+ ! Upload::type($image, array('jpg', 'jpeg', 'png', 'gif')))
+ {
+ return FALSE;
+ }
+
+ $directory = DOCROOT.'uploads/';
+
+ if ($file = Upload::save($image, NULL, $directory))
+ {
+ $filename = strtolower(Text::random('alnum', 20)).'.jpg';
+
+ $img = Image::factory($file);
+
+ // Crop the image square half the height and crop from center
+ $new_height = (int) $img->height / 2;
+
+ $img->crop($new_height, $new_height)
+ ->save($directory.$filename);
+
+ // Delete the temporary file
+ unlink($file);
+
+ return $filename;
+ }
+
+ return FALSE;
+ }
+
+}
+~~~
+
+The `index` action displays the upload form whereas the `do` action will process the uploaded image and provides feedback to the user.
+
+In `do` action, it checks if the request method was `POST`, then delegates the process to `_save_image()` method which in turn performs various checks and finally crops and saves the image to the `uploads` directory.
+
+## Views
+
+For the upload form (the `index` action), the view is located at `views/crop/index.php`.
+
+~~~
+<html>
+ <head>
+ <title>Upload Profile Image</title>
+ </head>
+ <body>
+ <h1>Upload your profile image</h1>
+ <form id="upload-form" action="<?php echo URL::site('crop/do') ?>" method="post" enctype="multipart/form-data">
+ <p>Choose file:</p>
+ <p><input type="file" name="avatar" id="avatar" /></p>
+ <p><input type="submit" name="submit" id="submit" value="Upload and crop" /></p>
+ </form>
+ </body>
+</html>
+~~~
+
+View for `crop/do` action goes to `views/crop/do.php`.
+
+~~~
+<html>
+ <head>
+ <title>Upload Profile Image Result</title>
+ </head>
+ <body>
+ <?php if ($uploaded_file): ?>
+ <h1>Upload success</h1>
+ <p>
+ Here is your uploaded and cropped avatar:
+ <img src="<?php echo URL::site("/uploads/$uploaded_file") ?>" alt="Uploaded avatar" />
+ </p>
+ <?php else: ?>
+ <h1>Something went wrong with the upload</h1>
+ <p><?php echo $error_message ?></p>
+ <?php endif ?>
+ </body>
+</html>
+~~~
+
+## Screenshots
+
+Below are screenshots for this example.
+
+![Original image](crop_orig.jpg)
+
+_Original image to upload_
+
+![Upload image form](crop_form.jpg)
+
+_Upload image form_
+
+![Upload result page](crop_result.jpg)
+
+_Upload result form_
@@ -0,0 +1,108 @@
+# Dynamic Image Controller
+
+In this example, we have images under `/uploads` under the webroot directory. We allow the user to render any image with dynamic dimension and is resized on the fly. It also caches the response for 1 hour to show basic caching mechanism.
+
+## Route
+
+First, we need a [Route]. This [Route] is based on this URL pattern:
+
+`/imagefly/filename/width/height` - where filename is the name of the image without the extension.
+
+This is our [Route] definition:
+
+~~~
+/**
+ * Set route for image fly
+ */
+Route::set('imagefly', 'imagefly/<image>/<width>/<height>', array('image' => '[-09a-zA-Z_]+', 'width' => '[0-9]+', 'height' => '[0-9]+'))
+ ->defaults(array(
+ 'controller' => 'imagefly',
+ 'action' => 'index'
+ ));
+~~~
+
+We ensure that the filename is only composed of letters, numbers and underscores, width and height must be numeric.
+
+## Controller
+
+Our controller simply accepts the request and capture the following parameters as defined by the [Route]:
+
+* `filename` - without the filename extension (and without dot)
+* `width`
+* `height`
+
+Then it finds the image file and when found, render it on the browser. Additional features added are browser caching.
+
+~~~
+<?php defined('SYSPATH') or die('No direct script access.');
+
+class Controller_Imagefly extends Controller {
+
+ public function action_index()
+ {
+ $file = $this->request->param('image');
+ $width = (int) $this->request->param('width');
+ $height = (int) $this->request->param('height');
+
+ $rendered = FALSE;
+ if ($file AND $width AND $height)
+ {
+ $filename = DOCROOT.'uploads/'.$file.'.jpg';
+
+ if (is_file($filename))
+ {
+ $this->_render_image($filename, $width, $height);
+ $rendered = TRUE;
+ }
+ }
+
+ if ( ! $rendered)
+ {
+ $this->response->status(404);
+ }
+ }
+
+ protected function _render_image($filename, $width, $height)
+ {
+ // Calculate ETag from original file padded with the dimension specs
+ $etag_sum = md5(base64_encode(file_get_contents($filename)).$width.','.$height);
+
+ // Render as image and cache for 1 hour
+ $this->response->headers('Content-Type', 'image/jpeg')
+ ->headers('Cache-Control', 'max-age='.Date::HOUR.', public, must-revalidate')
+ ->headers('Expires', gmdate('D, d M Y H:i:s', time() + Date::HOUR).' GMT')
+ ->headers('Last-Modified', date('r', filemtime($filename)))
+ ->headers('ETag', $etag_sum);
+
+ if (
+ $this->request->headers('if-none-match') AND
+ (string) $this->request->headers('if-none-match') === $etag_sum)
+ {
+ $this->response->status(304)
+ ->headers('Content-Length', '0');
+ }
+ else
+ {
+ $result = Image::factory($filename)
+ ->resize($width, $height)
+ ->render('jpg');
+
+ $this->response->body($result);
+ }
+ }
+}
+~~~
+
+When the parameters are invalid or the filename does not exists, it simply returns 404 not found error.
+
+The rendering of image uses some caching mechanism. One by setting the max age and expire headers and second by using etags.
+
+## Screenshots
+
+Visiting [http://localhost/kohana/imagefly/kitteh/400/400](http://localhost/kohana/imagefly/kitteh/400/400) yields:
+
+![Kitten 400x400](dynamic-400.jpg)
+
+Visiting [http://localhost/kohana/imagefly/kitteh/600/500](http://localhost/kohana/imagefly/kitteh/600/500) yields:
+
+![Kitten 400x400](dynamic-600.jpg)
Oops, something went wrong.

0 comments on commit 7f4dfbd

Please sign in to comment.