⭐ If you would like to buy me a coffee, well thank you very much that is mega kind! : https://www.buymeacoffee.com/honeyvig Hire a web Developer and Designer to upgrade and boost your online presence with cutting edge Technologies
Showing posts with label Images. Show all posts
Showing posts with label Images. Show all posts

Thursday, June 20, 2013

Solving Adaptive Images In Responsive Web Design

Adaptive images are the current hot topic in conversations about adaptive and responsive Web design. Why? Because no one likes any of the solutions thus far. New elements and attributes are being discussed as a solution for what is, for most of us, a big headache: to provide every user with one image optimized for their display size and resolution, without wasting time, memory or bandwidth with a client-side solution.
We have foreground and background images. We have large and small displays. We have regular and high-resolution displays. We have high-bandwidth and low-bandwidth connections. We have portrait and landscape orientations.
Some people waste bandwidth (and memory) by sending high-resolution images to all devices. Others send regular-resolution images to all devices, with the images looking less crisp on high-resolution displays.
What we really want to do is find the holy grail: the one solution that sends the image with the most appropriate size and resolution based on the browser and device making the request that can also be made accessible.
The “clown car” technique is the closest thing we’ve got to a holy grail: leveraging well-supported media queries, the SVG format and the <object> element to serve responsive images with a single request. The solution isn’t perfect yet, but it’s getting close.

Background Images And Media Queries

We’ve solved adaptive background images. Media queries make it simple to tailor the size and resolution of images to a device’s pixel ratio, viewport size and even screen orientation.
By using media queries with our background image styles, we can ensure that only the images that are needed are downloaded from the server. We can limit downloads to the assets that are most appropriate, saving bandwidth, memory and HTTP requests.
Unfortunately, there has been no solution for foreground images — until now. The technology has been available for a long time. The clown car technique is just a new technique that leverages existing technology.

Proposed Solutions With New Technology

New Elements and Attributes

With inline or “content” images, getting the browser to download and display only the appropriate foreground image is a bit more difficult. Most people believe that there is no mechanism for the <img> tag to cause an image of the right size and resolution to be downloaded. To that end, polyfills have been created and services have been established.
The <picture> element — which leverages the semantics of the HTML5 <video> element, with its support of media queries to swap in different source files — was proposed:
<picture alt="responsive image"> 
     <source src="large.jpg" media="(min-width:1600px),
     (min-resolution: 136dpi) and (min-width:800px)">
     <source src="medium.jpg" media="(min-width:800px),
     (min-resolution: 136dpi) and (min-width:400px)">
     <source src="small.jpg">
  <!-- fallback -->
  <img src="small.jpg" alt="responsive image">
</picture>
Another method, using a srcset attribute on the <img> element, has also been proposed. The above <picture> element would be written as this:
<img
    alt="responsive image"
    src="small.jpg" 
    srcset="large.jpg 1600w, 
          large.jpg 800w 1.95x, 
          medium.jpg 800w, 
          medium.jpg 400w 1.95x">
Both solutions have benefits and drawbacks. Picking one is hard — but we don’t have to anymore. The two solutions have been joined into what’s called “Florian’s Compromise.” However, the traction isn’t quite there yet.
Google has proposed client hints as part of HTTP headers, to enable the right image to be served server-side.

SVG as an Out-of-the-Box Solution

Many people don’t realize that we already have the technology to create and serve responsive images.
SVG has supported media queries for a long time, and browsers have supported SVG for… well, long enough, too. Most browsers support media queries in SVG (you can test your own browser). When it comes to responsive images, the only browsers in the mobile space that don’t support SVG are old versions of the Android browser (Android support for SVG began with Android 3.0).
We can leverage browser support for SVG and SVG support for both media queries and raster images to create responsive images, using media queries in SVG to serve up the right image.
My original experiment should theoretically work, and it does work in Internet Explorer (IE) 10 and Opera. When you mark up the HTML, you add a single call to an SVG file.
<img src="awesomefile.svg" alt="responsive image">
Now, isn’t that code simple?
SVGs support raster images included with the <image> element and with the CSS background-image property. In our responsive SVG, we would include all of the images that we might need to serve and then show only the appropriate image based on media queries.

Download a Single Raster Image

My first attempt at SVG used <image> with media queries, hiding them with display: none.
While the SVG works perfectly in terms of responsiveness, it has several problems. Unfortunately, setting display: none on an <image> in SVG, similar to <img> in HTML, does not prevent the resource from being downloaded. If you open the <image> SVG in your browser, all four PNGs will be retrieved from the server, making four HTTP requests, wasting bandwidth and memory.
We know from CSS background images that downloading only the images that are needed is indeed possible. Similarly, to prevent the SVG from downloading all of the included images, we would use CSS background images instead of foreground images in our SVG file:
<svg xmlns="http://www.w3.org/2000/svg" 
   viewBox="0 0 300 329" preserveAspectRatio="xMidYMid meet">

<title>Clown Car Technique</title>

<style>
svg {
 background-size: 100% 100%;
 background-repeat: no-repeat;
}

@media screen and (max-width: 400px) {
 svg {
  background-image: url(images/small.png");
 }
}

@media screen and (min-width: 401px) and (max-width: 700px) {
 svg {
  background-image: url(images/medium.png);
 }
}

@media screen and (min-width: 701px) and (max-width: 1000px) {
 svg {
  background-image: url(images/big.png);
 }
}

@media screen and (min-width: 1001px) {
 svg {
  background-image: url(images/huge.png);
 }
}
</style>
</svg>
The above can be included directly as an inline <svg>, or embedded with the <img> src  attribute or <object> data attribute.
If you’re familiar with media queries and CSS, most of the code above should make sense. The clown car technique uses the same media queries that you would use elsewhere on your adaptive website.
To preserve the aspect ratio of the containing element and ensure that is scales uniformly, we include the viewbox and preserveAspectRatio attributes.
The value of the viewbox attribute is a list of four space- or comma-separated numbers: min-x, min-y, width and height. By defining the width and height of our viewbox, we define the aspect ratio of the SVG image. The values we set for the preserveAspectRatio attribute — 300 × 329 — preserve the aspect ratio defined in viewbox.
Issues with including the above include 1) Chrome and Safari not maintaining the aspect ratio when <svg> is included inline: instead, defaulting the <svg> to 100% width and height. A bug has been submitted. 2) Webkit and Firefox not allowing the inclusion of raster images or scripts in SVGs embedded via the <img> element, and 3) No SVG support in IE <=8 and Android <=2.3.3.
When you open the SVG file with just background images defined, the raster image will take up the entire viewport. While the <image> version might look better as a standalone file because it is maintaining its aspect ratio and the background-image version is filling up the viewport, when you include the SVG as a separate document pulled into the HTML, the aspect ratio is preserved by default.  The background-size of containcover or 100% all work: choose the one that works best for your requirements.
The CSS background-image property solves the HTTP request problem. Open the SVG file with just PNG background images (or the JPEG version ) and look at the “Network” tab in your developer tools, and you’ll see that the SVG has made only two HTTP requests, rather than five. If your monitor is large, then the browser would have downloaded a small SVG file (676 bytes) and huge.png or huge.jpg.
Our first problem — that all of the different sizes of images are downloaded, even those that aren’t needed — has been resolved. This background-image version downloads only the image required, thereby addressing the concerns about multiple HTTP requests and wasted bandwidth.
The magic happens when we include SVG in a flexible layout. You’ll notice that the first time you resize the image, the browser might flicker white as it requests the next required PNG — because it doesn’t automatically download all assets. Rather, it downloads only the asset it needs. Simply declare either the width or the height of the container (<img>, <svg> or <object>) with CSS within your layout media queries, and the SVG will only pull in the single raster image it needs.
We still have the SVG file itself, which requires an HTTP request when not embedded inline with <svg>. We’ll solve that issue third.

Content Security Issues

In Opera or in Windows 9 or 10, open the HTML file containing an SVG raster image that is linked to with the <img> tag. Note in the “Resources” panel of the developer tools that only one JPEG or PNG is being downloaded. Resize your browser. Note that the <img> is responsive. Additional JPEGs or PNGs (we could also have used GIF or WebP) are downloaded only when needed.
If you opened the HTML file containing an SVG raster image in Firefox or WebKit, you would likely have seen no image. The SVG works in all modern browsers, but the <img> that calls in an SVG pulling in raster images works only in Opera and IE 9+. We’ll first cover how it works in IE and Opera, then we’ll cover the issues with WebKit and Firefox.
The code is simple:
<img src="awesomefile.svg" alt="responsive image">
When you include the SVG in your HTML <img> with a flexible width, such as 70% of the viewport, then as you grow and shrink the container by changing the window’s size or the CSS, the image will respond accordingly.
The width media query in the SVG is based on the parent element in which the SVG is contained — the <img>, in this case — not the viewport’s width.
As the window grows and shrinks, the image displayed by the SVG changes. In the SVG file, the images are defined as being 100% of the height and width of the parent, which, in the case above, when we opened the SVG directly, was the viewport. Now, the container is the <img> element. Because we included the viewbox and preserveAspectRatio attributes, as long as at least one length is defined, the SVG will grow or shrink to fit that length, maintaining the declared aspect ratio in the SVG, whatever the image’s size.
These foreground images work perfectly in Opera and IE 9+ (the versions found on mobile devices). In Chrome and Safari, if you open the SVG file first, thereby caching it, then the HTML file that contains the foreground SVG image might work as well.
While we saw earlier that the browser can indeed render the SVG, if the SVG is included in our document via the <img> tag, then this particular type of SVG will fail to render.
Why? To prevent cross-domain scripting attacks, some browsers have content security policies in place to keep SVG from importing media or scripts, in case they’re malicious in nature.
Blocking SVGs from importing scripts and images does make sense: To prevent cross-domain scripting attacks, you don’t want a file to pull potentially malicious content. So, SVG is supported, but in the case of WebKit and FireFox, it is just being prevented from pulling in external raster images. I’ve submitted a Chrome bug report to get the ban on importing raster images in SVG lifted.
In Firefox, the responsive SVG also works on its own. Firefox fully supports SVG. However, for security reasons, Firefox blocks the importing of external raster images, even if those images are on the same domain. The rationale is that allowing visitors to upload images and then displaying those images and scripts as part of an SVG constitutes a security risk. I would argue that if a website uses unsecured user-generated content, they’re already doing it wrong.
For right now, this simple line…
<img src="awesomefile.svg" alt="responsive image">
… is blocked in some browsers and, therefore, isn’t a viable solution.
All browsers support SVG media queries. They all support SVG as foreground or content images. They all support SVG as background images. The support just isn’t identical because of browser security policies.
All browsers do support the <object> tag. Without changing browser security policies, <img> alone won’t work yet. We can leverage the <object> tag.

With the <object> Tag

The <object> element allows an external resource to be treated as an image. It can take care of the browser security drawbacks we see with <img>, disallowing the importing of images or scripts into an <img> file. The <object> element allows both.
The code isn’t that much more complex:
<object data="awesomefile.svg" type="image/svg+xml"></object>
By default, the <object> will be as wide as the parent element. However, as with images, we can declare a width or height with the width or height attribute or with the CSS width or height property. For the clown car technique to maintain the declared aspect ratio, simply declare just one length value.
Because of the viewbox and preserveAspectRatio declarations in our SVG file, the <object> will by default maintain the declared aspect ratio. You can overwrite this with HTML or CSS attributes.
As noted earlier, the media queries in the SVG match the SVG’s container, not the viewport. The matched media queries in the SVG file will reflect the parent of the <object> tag, rather than the viewport.
If you look at an SVG being pulled in as the <object> data, you’ll see that it works in all browsers that support SVG.

With the <svg> Tag

Instead of including an external SVG file, you can also include the svg as inline content with the <svg> tag. The benefit is no additional http request for an external .svg file.
Unfortunately, Chrome and Safari render the SVG as a full screen block display element and appear to not support the preserveAspectRatio attribute when included this way (they do support preserveAspectRatio when the SVG is included via the <object> tag).
The other drawback is that, unlike  <object>, we can’t include fallback content for browsers that don’t support SVG. Instead we would need to include background-image, with height and width on the <svg> for browsers that don’t support SVG.

Fallback for IE

The <object> element is supported in all browsers, even mobile browsers. But this technique works only if the browser supports SVG as well. Therefore, it doesn’t work in IE 8 and below or in Android 2.3 and below. There is a fallback for these older browsers. Also, we are making two HTTP requests to pull in the correct image — one for the SVG file and one for the raster image that we want to show — there is a solution for this, too.
What makes <object> more interesting than <img> or  <svg> is that it is a non-empty element that can include fallback content when a browser fails to support the <object>’s data type. If you want, you can include an <img> tag nested in <object> for browsers that don’t support the SVG.
For IE 8 and below, we’ll include our medium-sized raster image because they’re generally displayed on monitors at a normal DPI:
<object data="awesomefile.svg" type="image/svg+xml"> 
  <img src="medium.png" alt="responsive image">
</object>
Unfortunately, content nested within <object> is downloaded even when the object is rendered and the nested content is not needed or rendered. This adds a download of the medium-sized image whether or not it is needed.
To handle this issue, we can use conditional comments for IE.
<object data="awesomefile.svg" type="image/svg+xml">
  <!--[if lte IE 8]>
  <img src="medium.png" alt="Fallback for IE">
  <![endif]-->
</object>

A Single HTTP Request

We’ve narrowed the SVG to download a single raster image. The <object> method  downloads both the raster  image and the SVG file. We have two HTTP requests instead of one. To prevent additional HTTP requests, we can create an SVG data URI, instead of calling in an external SVG file.
<object data="data:image/svg+xml,<svg viewBox='0 0 300 329' preserveAspectRatio='xMidYMid meet' xmlns='http://www.w3.org/2000/svg'><title>Clown Car Technique</title><style>svg{background-size:100% 100%;background-repeat:no-repeat;}@media screen and (max-width: 400px){svg{background-image:url(images/small.png);}}@media screen and (min-width: 401px) and (max-width:700px){svg{ background-image:url(images/medium.png);}}@media screen and (min-width: 701px) and (max-width:1000px){svg{background-image:url(images/big.png);}}@media screen and (min-width:1001px){svg{background-image:url(images/huge.png);}}</style></svg>" type="image/svg+xml">
  <!--[if lte IE 8]>
      <img src="images/medium.png" alt="Fallback for IE">
  <![endif]-->
</object>
The code above looks messy, but it’s simply data:image/svg+xml, followed by the contents of the SVG file, minified. It is the same code we would include had we used the content <svg>, but this method supports the preserveAspectRatio attribute of the SVG.
It works in all browsers that support SVG, except IE. While this is frustrating, it’s actually because Microsoft is trying to follow the specification to the letter. The specification states that the data URI must be escaped. So, to make all browsers, including IE 9 and 10, support the data URI, we escape it:
<object data="data:image/svg+xml,%3Csvg%20viewBox='0%200%20300%20329'%20preserveAspectRatio='xMidYMid%20meet'%20xmlns='http://www.w3.org/2000/svg'%3E%3Ctitle%3EClown%20Car%20Technique%3C/title%3E%3Cstyle%3Esvg%7Bbackground-size:100%25%20100%25;background-repeat:no-repeat;%7D@media%20screen%20and%20(max-width:400px)%7Bsvg%7Bbackground-image:url(images/small.png);%7D%7D@media%20screen%20and%20(min-width:401px)%7Bsvg%7Bbackground-image:url(images/medium.png);%7D%7D@media%20screen%20and%20(min-width:701px)%7Bsvg%7Bbackground-image:url(images/big.png);%7D%7D@media%20screen%20and%20(min-width:1001px)%7Bsvg%7Bbackground-image:url(images/huge.png);%7D%7D%3C/style%3E%3C/svg%3E"
type="image/svg+xml">
  <!--[if lte IE 8]>
    <img src="images/medium.png" alt="Fallback for IE">
  <![endif]-->
</object>
The markup is ugly, but it works!
Open up our first page and our second page, and then open up the developer tools to inspect the HTTP requests. You’ll notice two HTTP requests: the HTML file, and the PNG that the SVG pulls in. The inspector will show an entry for the SVG file as well. But notice that no HTTP request is being made: the status of the SVG is “success,” and the size over the network is 0 bytes, with the size of the data URI SVG coming in at under 600 bytes.

Landscape Vs. Portrait

Generally, content images are either landscape or portrait: faces are portrait, groups of people, products and sunsets are landscape. Some people object strongly to the clown car technique because they believe that images don’t change according to orientation. That isn’t necessarily true.
The magic of this technique is that the rendered image changes based on the size of the container. You could set your landscape foreground image to 33% or 240 pixels or whatever else, and your portrait object’s width to 25% or 180 pixels or whatever else. The object’s size is determined by the CSS for your HTML. The raster image served is based on the media queries that match the object’s size.
The aspect ratio remains the same, but you can control which raster image is served by changing the proportions of the SVG container, matching the media queries in the HTML’s CSS with the media queries in the SVG’s CSS.
If you do prefer to serve landscape foreground images when in landscape mode and portrait when in portrait mode, don’t use the preserveAspectRatio attribute. Instead, declare absolute heights and widths in your CSS for each breakpoint design size.

Other Benefits

Another benefit of the clown car technique is that all of the logic remains in the SVG file. Similar to how we separate content from presentation from behavior, this method enables us to separate image logic from content. The <img> or <object> element is part of the content, but the logic that makes it all work can be made separate from the content layer: The logic is in the SVG image, instead of polluting the CSS and HTML. This benefit may make some choose the non-data-URI version of the <object> method, in-spite of the extra http request, because it is so easy and clean.
The technique enables us to neatly organize our images, separating behavior from presentation from content from images. I envision the structure of responsive image files to be something like this:

images/
 clowns/
  small.png
  medium.png
  large.png
  svg.svg
 cars/
  small.png
  medium.png
  large.png
  svg.svg
 techniques/
  small.png
  medium.png
  large.png
  svg.svg
All of our assets for a single image live in a single separate directory. The image file names are consistent, while the folder names vary. We can have responsive foreground images without littering our HTML with extra unused code, thus making image management and updating a breeze.

Drawbacks Of The Clown Car Technique

We’ve covered the pros of the technique. The technique is still nascent, so I haven’t figured out all of the problems. I am working on solutions to some of the issues that have been found, and I assume new issues will arise.
I am mostly concerned with the ways in which images that arise from the clown car technique fail to behave like regular PNGs, JPEGs and GIFs. The main issues I have noticed are loading order, fallback for Android 2.3.3 and below, accessibility, and the ability to right-click on the image.

Page Layout

According to John Wilkins, the clown car technique requires the CSS layout to fully render before images start to load. I have not had a chance to compare the loading of regular foreground images versus the <object> element with SVG pulling in raster images, so I cannot comment on the impact of this issue yet.

Android 2.3 and Below

Android 2.3 and below does not support SVG. I have found three possible solutions, which I have yet to flesh out.
<SVG> with background-image
We can use <svg> as inline content instead of <object>. While  IE 8 and below and Android 2.3 and below do not support SVG, with CSS these browsers can give <svg> layout with height and width, and then declare the raster image as the background-image value.
As our goal is to create responsive foreground images without the use of CSS background images, this backward-compatibility hack doesn’t suit our purposes. If this is our solution, why not just use CSS background images instead of the Clown Car Technique for all browsers and devices?
Conditional Comments
The first is to include conditional comments to include a medium-sized fallback for IE 8 and below, and a small-sized fallback for all browsers that ignore conditional comments (including IE 10):
<!--[if lte IE 8]>
      <img src="images/medium.png" alt="Fallback for IE">
    <![endif]-->
    <!--[if !IE]> -->
      <img src="images/small.png" alt="fallback"/>
    <!-- <![endif]-->
This fallback will show the small PNG to all Android phones. Unfortunately, all browsers other than IE 9 and below will download small.png, even though the image will not be shown. In trying to solve for old Android, we are downloading the small PNG to most devices unnecessarily.
JavaScript Feature Detection
The other solution is feature detection with JavaScript — i.e. test for SVG support. Include a .no-svg class in the <html> element if the browser doesn’t support SVG. Using a WebKit-prefixed media query to exclude non-WebKit browsers, targeting as follows:
.no-svg object[type*="svg"] {
    width: 300px; 
    height: 329px; 
    background-image: url(small.png);
}
The properties above add dimensions and a background image to the <object> object. Again, I haven’t tested this yet, and it’s not accessible, which brings us to the next topic.

Accessibility

The benefit of <img> is that a simple alt attribute can make it accessible. The <object> element does not have an alt attribute. Ideas to make clown car images accessible include the following:
  • Add a <title> and <desc> to the SVG file.
  • Add ARIA role="img" and other ARIA attributes, such as aria-label and aria-labeled-by.
  • Include tab-index="0" to enable the <object> to gain focus without changing tab order.
  • Add alt attributes to the fallback images.
  • Add alternative text between the opening and closing <object> tags.
Mac OS X’s Universal Access’s VoiceOver reads the content of the SVG’s <title> and value of the   aria-label attribute on the <object> when the <object> includes tabindex="0".  Testing of the accessibility testing page still needs to be done with actual screen readers.

Right-Click to Save

When you right-click on an image in a desktop browser, you’ll get a menu enabling you to save the image. On some phones, a lingering touch on an image will prompt a save. This does not work with <object> or with background images, which is what our SVG is made of.
This “drawback” might be a feature for people who are wary of their images being stolen. In the brief time that I have contemplated this issue, I have yet to come up with a native way to resolve this issue. It is likely resolvable with JavaScript.
If the inability to right-click to save is your main argument against this technique, then recall that while users can right-click on WebP images in browsers that support WebP (only Chrome and Opera), they can’t do much with those images because native applications don’t support this new format. But this needn’t prevent us from moving forward with these bandwidth-saving techniques and features.

Why “Clown Car”?

This technique “clown car” because it includes many large images (the clowns) in a single tiny SVG image file (the car).
We need to use the non-semantic <object> element as we encourage browser vendors to support raster images in SVG as an <img> source either via CORS or CSP.
The clown car technique is a solution we can use now. Detractors argue that <picture> and/or srcset are the answer without convincing me that the clown car technique isn’t the right answer. Some argue that the lack of support in Android is its downfall, forgetting that Android 2.3.3 and IE 8 don’t support <picture> or srcset either.
I believe the <object> element can be made accessible. While the lack of semantics is a drawback, I will be satisfied using this technique once accessibility is assured. Testing accessibility is my next priority. While I would like to see this element work with the simpler and more semantic <img> tag, once the accessibility issue is resolved, this technique will be production-ready.

Wednesday, November 9, 2011

Image Manipulation With jQuery and PHP GD

One of the numerous advantages brought about by the explosion of jQuery and other JavaScript libraries is the ease with which you can create interactive tools for your site. When combined with server-side technologies such as PHP, this puts a serious amount of power at your finger tips.
In this article, I’ll be looking at how to combine JavaScript/jQuery with PHP and, particularly, PHP’s GD library to create an image manipulation tool to upload an image, then crop it and finally save the revised version to the server. Sure, there are plugins out there that you can use to do this; but this article aims to show you what’s behind the process. You can download the source files (updated) for reference.
We’ve all seen this sort of Web application before — Facebook, Flickr, t-shirt-printing sites. The advantages are obvious; by including a functionality like this, you alleviate the need to edit pictures manually from your visitors, which has obvious drawbacks. They may not have access to or have the necessary skills to use Photoshop, and in any case why would you want to make the experience of your visitors more difficult?


Before You Start

For this article, you would ideally have had at least some experience working with PHP. Not necessarily GD — I’ll run you through that part, and GD is very friendly anyway. You should also be at least intermediate level in JavaScript, though if you’re a fast learning beginner, you should be fine as well.
A quick word about the technologies you’ll need to work through this article. You’ll need a PHP test server running the GD library, either on your hosting or, if working locally, through something like XAMPP. GD has come bundled with PHP as standard for some time, but you can confirm this by running the phpinfo() function and verifying that it’s available on your server. Client-side-wise you’ll need a text editor, some pictures and a copy of jQuery.

Setting Up The Files

And off we go, then. Set up a working folder and create four files in it: index.php, js.js, image_manipulation.php and css.css. index.php is the actual webpage, js.js and css.css should be obvious, while image_manipulation.php will store the code that handles the uploaded image and then, later, saves the manipulated version.
In index.php, first let’s add a line of PHP to start a PHP session and call in our image_manipulation.php file:
1
After that, add in the DOCTYPE and skeleton-structure of the page (header, body areas etc) and call in jQuery and the CSS sheet via script and link tags respectively.
Add a directory to your folder, called imgs, which will receive the uploaded files. If you’re working on a remote server, ensure you set the permissions on the directory such that the script will be able to save image files in it.
First, let’s set up and apply some basic styling to the upload facility.

The Upload Functionality

Now to some basic HTML. Let’s add a heading and a simple form to our page that will allow the user to upload an image and assign that image a name:
1<h1>Image uploader and manipulatorh1>
2<form method="POST" action="index.php" enctype="multipart/form-data" id="imgForm">
3    <label for="img_upload">Image on your PC to uploadlabel>
4<input name="img_upload" id="img_upload" type="file">
5 
6    <label for="img_name">Give this image a namelabel>
7<input name="img_name" id="img_name" type="text">
8<input name="upload_form_submitted" type="submit">
9form>
Please note that we specify enctype=’multipart/form-data’ which is necessary whenever your form contains file upload fields.
As you can see, the form is pretty basic. It contains 3 fields: an upload field for the image itself, a text field, so the user can give it a name and a submit button. The submit button has a name so it can act as an identifier for our PHP handler script which will know that the form was submitted.
Let’s add a smattering of CSS to our stylesheet:
1/* -----------------
2| UPLOAD FORM
3----------------- */
4#imgForm { border: solid 4px #ddd; background: #eee; padding: 10px; margin: 30px; width: 600px; overflow:hidden;}
5    #imgForm label { float: left; width: 200px; font-weight: bold; color: #666; clear:both; padding-bottom:10px; }
6    #imgForm input { float: left; }
7    #imgForm input[type="submit"] {clear: both; }
8    #img_upload { width: 400px; }
9    #img_name { width: 200px; }
Now we have the basic page set up and styled. Next we need to nip into image_manipulation.php and prepare it to receive the submitted form. Which leads nicely on to validation…

Validating The Form

Open up image_manipulation.php. Since we made a point above of including it into our HTML page, we can rest assured that when it’s called into action, it will be present in the environment.
Let’s set up a condition, so the PHP knows what task it is being asked to do. Remember we named our submit button upload_form_submitted? PHP can now check its existence, since the script knows that it should start handling the form.
This is important because, as I said above, the PHP script has two jobs to do: to handle the uploaded form and to save the manipulated image later on. It therefore needs a technique such as this to know which role it should be doing at any given time.
1/* -----------------
2| UPLOAD FORM - validate form and handle submission
3----------------- */
4 
5if (isset($_POST['upload_form_submitted'])) {
6    //code to validate and handle upload form submission here
7}
So if the form was submitted, the condition resolves to true and whatever code we put inside, it will execute. That code will be validation code. Knowing that the form was submitted, there are now five possible obstacles to successfully saving the file: 1) the upload field was left blank; 2) the file name field was left blank; 3) both these fields were filled in, but the file being uploaded isn’t a valid image file; 4) an image with the desired name already exists; 5) everything is fine, but for some reason, the server fails to save the image, perhaps due to file permission issues. Let’s look at the code behind picking up each of these scenarios, should any occur, then we’ll put it all together to build our validation script.
Combined into a single validation script, the whole code looks as follows.
01/* -----------------
02| UPLOAD FORM - validate form and handle submission
03----------------- */
04 
05if (isset($_POST['upload_form_submitted'])) {
06 
07    //error scenario 1
08    if (!isset($_FILES['img_upload']) || empty($_FILES['img_upload']['name'])) {
09        $error = "Error: You didn't upload a file";
10 
11    //error scenario 2
12    } else if (!isset($_POST['img_name']) || empty($_FILES['img_upload'])) {
13        $error = "Error: You didn't specify a file name";
14    } else {
15 
16        $allowedExtensions = array('jpg', 'jpeg', 'gif', 'png');
17        preg_match('/\.('.implode($allowedExtensions, '|').')$/', $_FILES['img_upload']['name'], $fileExt);
18        $newPath = 'imgs/'.$_POST['img_name'].'.'.$fileExt[0];
19 
20        //error scenario 3
21        if (file_exists($newPath)) {
22            $error = "Error: A file with that name already exists";
23 
24        //error scenario 4
25        } else if (!in_array(substr($fileExt[0], 1), $allowedExtensions)) {
26            $error = 'Error: Invalid file format - please upload a picture file';
27 
28        //error scenario 5
29        } else if (!copy($_FILES['img_upload']['tmp_name'], $newPath)) {
30            $error = 'Error: Could not save file to server';
31 
32        //...all OK!
33        } else {
34            $_SESSION['newPath'] = $newPath;
35            $_SESSION['fileExt'] = $fileExt;
36        }
37    }
38}
There are a couple of things to note here.

$error & $_SESSION['newPath']

Firstly, note that I’m using a variable, $error, to log whether we hit any of the hurdles. If no error occurs and the image is saved, we set a session variable, $_SESSION['new_path'], to store the path to the saved image. This will be helpful in the next step where we need to display the image and, therefore, need to know its SRC.
I’m using a session variable rather than a simple variable, so when the time comes for our PHP script to crop the image, we don’t have to pass it a variable informing the script which image to use — the script will already know the context, because it will remember this session variable. Whilst this article doesn’t concern itself deeply with security, this is a simple precaution. Doing this means that the user can affect only the image he uploaded, rather than, potentially, someone else’s previously-saved image — the user is locked into manipulating only the image referenced in $error and has no ability to enforce the PHP script to affect another image.

The $_FILES superglobal

Note that even though the form was sent via POST, we access the file upload not via the $_POST superglobal (i.e. variables in PHP which are available in all scopes throughout a script), but via the special $_FILES superglobal. PHP automatically assigns file fields to that, provided the form was sent with the required enctype='multipart/form-data' attribute. Unlike the $_POST and $_GET superglobals, the $_FILES superglobal goes a little “deeper” and is actually a multi-dimensional array. Through this, you can access not only the file itself but also a variety of meta data related to it. You’ll see how we can use this information shortly. We use this meta data in the third stage of validation above, namely checking that the file was a valid image file. Let’s look at this code in a little more detail.

Confirming the upload is an image

It’s sensible that we don’t allow the user to proceed if the uploaded file is not an image. So we need to look out for this. First, we create an array of allowed file extensions:
1$allowedExtensions = array('jpg', 'jpeg', 'gif', 'png');
We then check whether or not the extension of the uploaded file is in that array. To do this, we of course need to extract the file extension. Surprisingly, the superglobal doesn’t provide this directly, so instead we’ll extract it with a regular expression.
Regular expressions are typically considered one of the hardest parts of programming to master. This is definitely true, yet they are often extremely valuable. If you want to read up more on regular expressions, take a look at Smashing Magazine’s articles Crucial Concepts Behind Advanced Regular Expressions or the excellent Regular-Expressions.info. The concept is essentiall matching patterns within strings. We know that our extension is the final part of the final name, preceded by a dot, so that forms the basis of our pattern:
1preg_match('/\.('.implode($allowedExtensions, '|').')$/', $_FILES['img_upload']['name'], $fileExt);
preg_match() is the preferred function in PHP to match via REGEXP. It takes three arguments: the pattern, the string to look in, and an array to save matches to. So if a match is found — and of course it should be — our file extension will live in $fileExt[0], i.e. the first and only key of the array of matches.
Patterns are expressed as strings, and inside forward slashes (usually, but not always), so please ignore these parts. Our actual pattern starts with the dot. It has a backslash before it as it needs escaping, because otherwise it would be read as a special character (unescaped dots denote wildcard characters in the regular expression syntax). This is no different to having to escape quotes when using them inside strings, e.g.
1"...and then he said \"hello, there\"";
The next part says: match any ONE of our allowed extensions. Since these live in our array, we convert them into a string via the implode() function, separated by the pipe character. Finally, the dollar character forces the expression to match the end of the string — required in our case, since a file extension is always at the end of a filename. So by the time the PHP engine has evaluated this pattern, it looks as though we had specified this (which is much more readable):
1'/\.(jpg|jpeg|gif|png)$/'

Saving the file

All uploaded files are assigned a temporary home by the server until such time as the session expires or they are moved. So saving the file means moving the file from its temporary location to a permanent home. This is done via the copy() function, which needs to know two rather obvious things: what’s the path to the temporary file, and what’s the path to where we want to put it.
The answer to the first question is read from the tmp_name part of the $_FILES superglobal. The answer to the second is the full path, including new filename, to where you want it to live. So it is formed of the name of the directory we set up to store images (/imgs), plus the new file name (i.e. the value entered into the img_name field) and the extension. Let’s assign it to its own variable, $newPath and then save the file:
1$newPath = 'imgs/'.$_POST['img_name'].'.'.$fileExt;
2...
3copy($_FILES['img_upload']['tmp_name'],$newPath);

Reporting Back and Moving On

What happens next depends entirely on whether an error occurred, and we can find it out by looking up whether $error is set. If it is, we need to communicate this error back to the user. If it’s not set, it’s time to move on and show the image and let the user manipulate it. Add the following above your form:
1if (isset($error)) echo '
'.$error.'
'; ?>
If there’s an error, we’d want to show the form again. But the form is currently set to show regardless of the situation. This needs to change, so that it shows only if no image has been uploaded yet, i.e. if the form hasn’t been submitted yet, or if it has but there was an error. We can check whether an uploaded image has been saved by interrogating the $_SESSION['newPath'] variable. Wrap your form HTML in the following two lines of code:
1if (!isset($_SESSION['newPath']) || isset($_GET['true'])) { ?>
2 
3else echo '.$_SESSION['newPath'].'" />'; ?>
Now the form appears only if an uploaded image isn’t registered — i.e. $_SESSION['newPath'] isn’t set — or if new=true is found in the URL. (This latter part provides us with a means of letting the user start over with a new image upload should they wish so; we’ll add a link for this in a moment). Otherwise, the uploaded image displays (we know where it lives because we saved its path in $_SESSION['newPath']).
This is a good time to take stock of where we are, so try it out. Upload an image, and verify that that it displays. Assuming it does, it’s time for our JavaScript to provide some interactivity for image manipulation.

Adding Interactivity

First, let’s extend the line we just added so that we a) give the image an ID to reference it later on; b) call the JavaScript itself (along with jQuery); and c) we provide a “start again” link, so the user can start over with a new upload (if necessary). Here is the code snippet:
1else { ?>
2    "uploaded_image" src="" />
4 
5    
6    
7    
8
Note that I defined an ID for the image, not a class, because it’s a unique element, and not one of the many (this sounds obvious, but many people fail to observe this distinction when assigning IDs and classes). Note also, in the image’s SRC, I’m appending a random string. This is done to force the browser not to cache the image once we’ve cropped it (since the SRC doesn’t change).
Open js.js and let’s add the obligatory document ready handler (DRH), required any time you’re using freestanding jQuery (i.e. not inside a custom function) to reference or manipulate the DOM. Put the following JavaScript inside this DRH:
1$(function() {
2    // all our JS code will go here
3});
We’re providing the functionality to a user to crop the image, and it of course means allowing him to drag a box area on the image, denoting the part he wishes to keep. Therefore, the first step is to listen for a mousedown event on the image, the first of three events involved in a drag action (mouse down, mouse move and then, when the box is drawn, mouse up).
1var dragInProgress = false;
2 
3$("#uploaded_image").mousedown(function(evt) {
4    dragInProgress = true;
5});
And in similar fashion, let’s listen to the final mouseup event.
1$(window).mouseup(function() {
2    dragInProgress = false;
3});
Note that our mouseup event runs on window, not the image itself, since it’s possible that the user could release the mouse button anywhere on the page, not necessarily on the image.
Note also that the mousedown event handler is prepped to receive the event object. This object holds data about the event, and jQuery always passes it to your event handler, whether or not it’s set up to receive it. That object will be crucial later on in ascertaining where the mouse was when the event fired. The mouseup event doesn’t need this, because all we care about if is that the drag action is over and it doesn’t really matter where the mouse is.
We’re tracking whether or not the mouse button is currently depressed in a variable, . Why? Because, in a drag action, the middle event of the three (see above) only applies if the first happened. That is, in a drag action, you move the mouse whilst the mouse is down. If it’s not, our mousemove event handler should exit. And here it is:
1$("#uploaded_image").mousemove(function(evt) {
2    if (!dragInProgress) return;
3});
So now our three event handlers are set up. As you can see, the mousemove event handler exits if it discovers that the mouse button is not currently down, as we decided above it should be.
Now let’s extend these event handlers.
This is a good time to explain how our JavaScript will be simulating the drag action being done by the user. The trick is to create a DIV on mousedown, and position it at the mouse cursor. Then, as the mouse moves, i.e. the user is drawing his box, that element should resize consistently to mimic that.
Let’s add, position and style our DIV. Before we add it, though, let’s remove any previous such DIV, i.e. from a previous drag attempt. This ensures there’s only ever one drag box, not several. Also, we want to log the mouse coordinates at the time of mouse down, as we’ll need to reference these later when it comes to drawing and resizing ourDIV. Extend the mousedown event handler to become:
1$("#uploaded_image").mousedown(function(evt) {
2    dragInProgress = true;
3    $("#drag_box").remove();
4    $("
").appendTo("body").attr("id", "drag_box").css({left: evt.clientX, top: evt.clientY});
5    mouseDown_left = evt.clientX;
6    mouseDown_top = evt.clientY;
7});
Notice that we don’t prefix the three variables there with the 'var' keyword. That would make them accessible only within the mousedown handler, but we need to reference them later in our mousemove handler. Ideally, we’d avoid global variables (using a namespace would be better) but for the purpose of keeping the code in this tutorial concise, they’ll do for now.
Notice that we obtain the coordinates of where the event took place — i.e. where the mouse was when the mouse button was depressed — by reading the clientX and clientY properties of the event object, and it’s those we use to position our DIV.
Let’s style the DIV by adding the following CSS to your stylesheet.
1#drag_box { position: absolute; border: solid 1px #333; background: #fff; opacity: .5; filter: alpha(opacity=50); z-index: 10; }
Now, if you upload an image and then click it, the DIV will be inserted at your mouse position. You won’t see it yet, as it’s got width and height zero; only when we start dragging should it become visible, but if you use Firebug or Dragonfly to inspect it, you will see it in the DOM.
So far, so good. Our drag box functionality is almost complete. Now we just need to make it respond to the user’s mouse movement. What’s involved here is very much what we did in the mousedown event handler when we referenced the mouse coordinates.
The key to this part is working out what properties should be updated, and with what values. We’ll need to change the box’s left, top, width and height.
Sounds pretty obvious. However, it’s not as simple as it sounds. Imagine that the box was created at coordinates 40×40 and then the user drags the mouse to coordinates 30×30. By updating the box’s left and top properties to 30 and 30, the position of the top left corner of the box would be correct, but the position of its bottom right corner would not be where the mousedown event happened. The bottom corner would be 10 pixels north west of where it should be!
To get around this, we need to compare the mousedown coordinates with the current mouse coordinates. That’s why in our mousedown handler, we logged the mouse coordinates at the time of mouse down. The box’s new CSS values will be as follows:
  • left: the lower of the two clientX coordinates
  • width: the difference between the two clientX coordinates
  • top: the lower of the two clientY coordinates
  • height: the difference between the two clientY coordinates
So let’s extend the mousemove event handler to become:
1$("#uploaded_image").mousemove(function(evt) {
2    if (!dragInProgress) return;
3    var newLeft = mouseDown_left < evt.clientX ? mouseDown_left : evt.clientX;
4    var newWidth = Math.abs(mouseDown_left - evt.clientX);
5    var newTop = mouseDown_top < evt.clientY ? mouseDown_top : evt.clientY;
6    var newHeight = Math.abs(mouseDown_top - evt.clientY);
7    $('#drag_box').css({left: newLeft, top: newTop, width: newWidth, height: newHeight});
8});
Notice also that, to establish the new width and height, we didn't have to do any comparison. Although we don't know, for example, which is lower out of the mousedown left and the current mouse left, we can subtract either from the other and counter any negative result by forcing the resultant number to be positive via Math.abs(), i.e.
1result = 50 – 20; //30
2result = Math.abs(20 – 50); //30 (-30 made positive)
One final, small but important thing. When Firefox and Internet Explorer detect drag attempts on images they assume the user is trying to drag out the image onto their desktop, or into Photoshop, or wherever. This has the potential to interfere with our creation. The solution is to stop the event from doing its default action. The easiest way is to return false. What's interesting, though, is that Firefox interprets drag attempts as beginning on mouse down, whilst IE interprets them as beginning on mouse move. So we need to append the following, simple line to the ends of both of these functions:
1return false;
Try your application out now. You should have full drag box functionality.

Saving the Cropped Image

And so to the last part, saving the modified image. The plan here is simple: we need to grab the coordinates and dimensions of the drag box, and pass them to our PHP script which will use them to crop the image and save a new version.

Grabbing the drag box data

It makes sense to grab the drag box's coordinates and dimensions in our mouseup handler, since it denotes the end of the drag action. We could do that with the following:
1var db = $("#drag_box");
2var db_data = {left: db.offset().left, top: db.offset().top, width: db.width(), height: db.height()};
There's a problem, though, and it has to do with the drag box's coordinates. The coordinates we grab above are relative to the body, not the uploaded image. So to correct this, we need to subtract the position, relative to the body, of the image itself, from them. So let's add this instead:
1var db = $("#drag_box");
2if (db.width() == 0 || db.height() == 0 || db.length == 0) return;
3var img_pos = $('#uploaded_image').offset();
4var db_data = {
5    left: db.offset().left – img_pos.left,
6    top: db.offset().top - img_pos.top,
7    width: db.width(),
8    height: db.height()
9};
What's happening there? We're first referencing the drag box in a local shortcut variable, db, and then store the four pieces of data we need to know about it, its left, top, width and height, in an object db_data. The object isn't essential: we could use separate variables, but this approach groups the data together under one roof and might be considered tidier.
Note the condition on the second line, which guards against simple, dragless clicks to the image being interpreted as crop attempts. In these cases, we return, i.e. do nothing.
Note also that we get the left and top coordinates via jQuery's offset() method. This returns the dimensions of an object relative to the document, rather than relative to any parent or ancestor with relative positioning, which is what position() or css('top/right/bottom/left') would return. However, since we appended our drag box directly to the body, all of these three techniques would work the same in our case. Equally, we get the width and height via the width() and height() methods, rather than via css('width/height'), as the former omits 'px' from the returned values. Since our PHP script will be using these coordinates in a mathematical fashion, this is the more suitable option.
For more information on the distinction between all these methods, see my previous article on SmashingMag, Commonly Confused Bits of jQuery.
Let's now throw out a confirm dialogue box to check that the user wishes to proceed in cropping the image using the drag box they've drawn. If so, time to pass the data to our PHP script. Add a bit more to your mouseup handler:
1if (confirm("Crop the image using this drag box?")) {
2    location.href = "index.php?crop_attempt=true&crop_l="+db_data.left+"&crop_t="+
3db_data.top+"&crop_w="+db_data.width+"&crop_h="+db_data.height;
4} else {
5    db.remove();
6}
So if the user clicks 'OK' on the dialogue box that pops up, we redirect to the same page we're on, but passing on the four pieces of data we need to give to our PHP script. We also pass it a flag crop_attempt, which our PHP script can detect, so it knows what action we'd like it to do. If the user clicks 'Cancel', we remove the drag box (since it's clearly unsuitable). Onto the PHP...

PHP: saving the modified file

Remember we said that our image_manipulation.php had two tasks — one to first save the uploaded image and another to save the cropped version of the image? It's time to extend the script to handle the latter request. Append the following to image_manipulation.php:
1/* -----------------
2| CROP saved image
3----------------- */
4 
5if (isset($_GET["crop_attempt"])) {
6    //cropping code here
7}
So just like before, we condition-off the code area and make sure a flag is present before executing the code. As for the code itself, we need to go back into the land of GD. We need to create two image handles. Into one, we import the uploaded image; the second one will be where we paste the cropped portion of the uploaded image into, so we can essentially think of these two as source and destination. We copy from the source onto the destination canvas via the GD function imagecopy(). This needs to know 8 pieces of information:
  • destination, the destination image handle
  • source, the source image handle
  • destination X, the left position to paste TO on the destination image handle
  • destination Y, the top position “ “ “ “
  • source X, the left position to grab FROM on the source image handle
  • source Y, the top position “ “ “ “
  • source W, the width (counting from source X) of the portion to be copied over from the source image handle
  • source H, the height (counting from source Y) “ “ “ “
Fortunately, we already have the data necessary to pass to the final 6 arguments in the form of the JavaScript data we collected and passed back to the page in our mouseup event handler a few moments ago.
Let's create our first handle. As I said, we'll import the uploaded image into it. That means we need to know its file extension, and that's why we saved it as a session variable earlier.
01switch($_SESSION["fileExt"][1]) {
02    case "jpg": case "jpeg":
03        var source_img = imagecreatefromjpeg($_SESSION["newPath"]);
04        break;
05    case "gif":
06        var source_img = imagecreatefromgif($_SESSION["newPath"]);
07        break;
08    case "png":
09        var source_img = imagecreatefrompng($_SESSION["newPath"]);
10        break;
11}
As you can see, the file type of the image determines which function we use to open it into an image handle. Now let's extend this switch statement to create the second image handle, the destination canvas. Just as the function for opening an existing image depends on image type, so too does the function used to create a blank image. Hence, let's extend our switch statement:
01switch($_SESSION["fileExt"][1]) {
02    case "jpg": case "jpeg":
03        $source_img = imagecreatefromjpeg($_SESSION["newPath"]);
04        $dest_ing = imagecreatetruecolor($_GET["crop_w"], $_GET["crop_h"]);
05        break;
06    case "gif":
07        $source_img = imagecreatefromgif($_SESSION["newPath"]);
08        $dest_ing = imagecreate($_GET["crop_w"], $_GET["crop_h"]);
09        break;
10    case "png":
11        $source_img = imagecreatefrompng($_SESSION["newPath"]);
12        $dest_ing = imagecreate($_GET["crop_w"], $_GET["crop_h"]);
13        break;
14}
You'll notice that the difference between opening a blank image and opening one from an existing or uploaded file is that, for the former, you must specify the dimensions. In our case, that's the width and height of the drag box, which we passed into the page via the $_GET['crop_w'] and $_GET['crop_h'] vars respectively.
So now we have our two canvases, it's time to do the copying. The following is one function call, but since it takes 8 arguments, I'm breaking it onto several lines to make it readable. Add it after your switch statement:
01imagecopy(
02    $dest_img,
03    $source_img,
04    0,
05    0,
06    $_GET["crop_l"],
07    $_GET["crop_t"],
08    $_GET["crop_w"],
09    $_GET["crop_h"]
10);
The final part is to save the cropped image. For this tutorial, we'll overwrite the original file, but you might like to extend this application, so the user has the option of saving the cropped image as a separate file, rather than losing the original.
Saving the image is easy. We just call a particular function based on (yes, you guessed it) the image's type. We pass in two arguments: the image handle we're saving, and the file name we want to save it as. So let's do that:
1switch($_SESSION["fileExt"][1]) {
2    case "jpg": case "jpeg":
3        imagejpeg($dest_img, $_SESSION["newPath"]); break;
4    case "gif":
5        imagegif($dest_img, $_SESSION["newPath"]); break;
6    case "png":
7        imagepng($dest_img, $_SESSION["newPath"]); break;
8}
It's always good to clean up after ourselves - in PHP terms that means freeing up memory, so let's destroy our image handlers now that we don't need them anymore.
1imagedestroy($dest_img);
2imagedestroy($source_img);
Lastly, we want to redirect to the index page. You might wonder why we'd do this, since we're on it already (and have been the whole time). The trick is that by redirecting, we can lose the arguments we passed in the URL. We don't want these hanging around because, if the user refreshes the page, he'll invoke the PHP crop script again (since it will detect the arguments). The arguments have done their job, so now they have to go, so we redirect to the index page without these arguments. Add the following line to force the redirect:
1header("Location: index.php"); //bye bye arguments

Final Touches

So that's it. We now have a fully-working facility to first upload then crop an image, and save it to the server. Don't forget you can download the source files (updated) for your reference.
There's plenty of ways you could extend this simple application. Explore GD (and perhaps other image libraries for PHP); you can do wonders with images, resizing them, distorting them, changing them to greyscale and much more. Another thing to think about would be security; this tutorial does not aim to cover that here, but if you were working in a user control panel environment, you'd want to make sure the facility was secure and that the user could not edit other user's files.
With this in mind, you might make the saved file's path more complex, e.g. if the user named it pic.jpg, you might actually name it on the server 34iweshfjdshkj4r_pic.jpg. You could then hide this image path, e.g. by specifying the SRC attribute as 'getPic.php' instead of referencing the image directly inside an image's SRC attribute. That PHP script would then open and display the saved file (by reading its path in the session variable), and the user would never be aware of its path.
The possibilities are endless, but hopefully this tutorial has given you a starting point.