Mark’s PHP Snippets

These are just a few PHP snippets/functions I have written over the years and have found to be quite useful.

mysql_connect('localhost','USERNAME','PASSWORD');
mysql_select_db('DATABASE');

This one really isn’t anything special, but it makes its way into every single one of my pages, so it’s worth mentioning in case you forget the syntax or are new to PHP. It simply connects to a mysql database.

function mysql_safe_string($value) {
    if(empty($value))           return 'NULL';
    elseif(is_string($value))   return '\''.mysql_real_escape_string(trim($value)).'\'';
    elseif(is_numeric($value))  return $value;
    elseif(is_array($value))    return implode(',',array_map('mysql_safe_string',$value));
    else                        return false;
}

function mysql_safe_query($format) {
    $args = array_slice(func_get_args(),1);
    $args = array_map('mysql_safe_string',$args);
    $query = vsprintf($format,$args);
    return mysql_query($query);
}

These are my favorite two functions that I can’t live without. They make writing mysql queries so much easier.

// compare something like this
mysql_query(sprintf('INSERT INTO users (name, age) VALUES (%s, %d)', mysql_real_escape_string($_POST['name']), (is_numeric($_POST['age'])?$_POST['age']:'NULL')));
// to this
mysql_safe_query('INSERT INTO users (name, age) VALUES (%s, %s)', $_POST['name'], $_POST['age']);
// it even handles arrays nicely
$arr = array(1,2,3,4,5);
mysql_safe_query('SELECT * FROM posts WHERE cat_id IN (%s) ORDER BY date DESC', $arr);

These next two are quite simple, but still very handy.

function redirect($uri) {
    header('location:'.$uri);
    exit;
}

function pr($arr) {
    echo '<pre>';
    print_r($arr);
    echo '</pre>';
}

pr() is great for debugging, and redirect() helps keep your code clean and meaningful.

This next function is still in its infancy, so it may be refined over time.

function smart_excerpt($text, $maxLen=500) {
    if(preg_match('`.*?<!--\s*more\s*-->`is', $text, $matches))
        return $matches[0];
    if(strlen($text) > $maxLen) {
        $minLen = round($maxLen/2);
        if(preg_match('`.{'.$minLen.','.$maxLen.'}[.!?]`s', $text, $matches))
            return rtrim($matches[0]);
        return rtrim(substr($text, 0, $maxLen),' \t\n\r\0\x0B.!?').'...';
    }
    return false;
}

It accepts a body of text, and searches for the text “<!–more–>”. If it finds it, it returns everything before that. Otherwise, if it doesn’t, and it’s longer than $maxLen, it trims the text down to the first punctuation character (.!?) before $maxLen. That way your text doesn’t get cropped mid-sentence/word. If it can’t find any punctuation (because you’re a terrible writer?), it will crop at exactly $maxLen. It returns false if the text doesn’t need cropping. This way you can decide if you want to print a “view more” link or not.

// example usage
if($excerpt = smart_excerpt($post['body']))
    echo nl2br($excerpt).' <a href="post_view.php?id='.$post['id'].'">More &raquo;</a>';
else
    echo nl2br($post['body']);

Well, that’s all for now. I’ll post up some more if I find any other tasty tidbits lying around.

Create a Blog in 30 Minutes Without a Framework

A lot of PHP frameworks like to boast “create a blog in 20 minutes”. Well, I’m going to show you how to create a bare bones blog in about 30, but without the use of a framework. Our blog will include posts, and comments. I won’t bother with HTML headers and footers – you can add those in yourself. I recommend using my XHTML template; most of the pages below can easily be inserted into the body. I also won’t be covering user authentication in this tutorial, I’ll cover that in a later tutorial. We also won’t be doing much error checking, or prevention against users inserting malicious code. We will, however, prevent SQL injection with a very simple function.

What you will need:

  • A web server with PHP and MySQL support
  • Very basic programming knowledge

Topics covered:

  • Using MySQL to insert, delete, and update posts and comments
  • Iterating over MySQL results
  • Using cookies to save commenter information
  • Protection against SQL injection
  • Examples of php’s date() function
  • Using anchors to scroll the page
  • Redirection
  • Heredocs

Not covered:

  • User authentication (coming later)
  • Categories and tags (coming later)
  • Prevention against other (JavaScript) injection attacks
  • Error checking (empty fields, invalid emails, etc.)

The first thing we need to do is create some MySQL tables so that we have some place to store our data. We will be using two tables for our blog: posts, and comments. I will show you how to add categories and users in later tutorials.

If you haven’t already done so, you will need to create a MySQL database for your blog. I cover this in step 4 of my WordPress tutorial. Make note of your database name, username and password; you will need these in a few minutes.

Now create the following tables. You can do so by running the following code in the “SQL” tab of phpMyAdmin if you have it installed.

CREATE TABLE `posts` (
  `id` INT(11) NOT NULL AUTO_INCREMENT,
  `title` VARCHAR(255) NOT NULL,
  `body` text NOT NULL,
  `num_comments` INT(11) NOT NULL DEFAULT '0',
  `date` INT(11) NOT NULL,
  PRIMARY KEY  (`id`)
) ENGINE=MyISAM  DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
CREATE TABLE `comments` (
  `id` INT(11) NOT NULL AUTO_INCREMENT,
  `post_id` INT(11) NOT NULL,
  `name` VARCHAR(255) NOT NULL,
  `email` VARCHAR(255) NOT NULL,
  `website` VARCHAR(255) DEFAULT NULL,
  `content` text NOT NULL,
  `date` INT(11) NOT NULL,
  PRIMARY KEY  (`id`)
) ENGINE=MyISAM  DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

You may have noticed “date” is an int rather than datetime. I prefer to use a unix timestamp instead because they are easier to work with in PHP, but you can use whatever you choose — just keep in mind you will have to modify some of the code below if you choose to use datetime instead.

Now open your favorite text editor, and get ready to start coding! I use EditPlus, but there are plenty of free alternatives. We will be creating the following pages:

  • index.php
  • post_add.php
  • post_delete.php
  • post_edit.php
  • post_view.php
  • comment_add.php
  • comment_delete.php
  • mysql.php

You can create these files now if you like, and we’ll slowly fill them in. Don’t worry, most of them are just a few lines of code!

Let’s start with the most important file: mysql.php. This file simply connects to our database so that we can run queries on it. I’ve also included a few simple helper functions. I don’t know how people get by without these!

<?php
// mysql.php
function mysql_safe_string($value) {
    $value = trim($value);
    if(empty($value))           return 'NULL';
    elseif(is_numeric($value))  return $value;
    else                        return "'".mysql_real_escape_string($value)."'";
}

function mysql_safe_query($query) {
    $args = array_slice(func_get_args(),1);
    $args = array_map('mysql_safe_string',$args);
    return mysql_query(vsprintf($query,$args));
}

function redirect($uri) {
    header('location:'.$uri);
    exit;
}

mysql_connect('localhost','USERNAME','PASSWORD');
mysql_select_db('DATABASE');

If you hadn’t noticed, you will need to fill in your username, password and database name. mysql_safe_query is basically a combination of mysql_query, sprintf, and some sanitization. It converts empty strings to NULL, leaves numbers as they are, and quotes and properly escapes strings as necessary so that you don’t have to worry about SQL injection attacks.

Now let’s add the ability to make some blog posts!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<?php
// post_add.php
if(!empty($_POST)) {
    include 'mysql.php';
    if(mysql_safe_query('INSERT INTO posts (title,body,date) VALUES (%s,%s,%s)', $_POST['title'], $_POST['body'], time()))
        echo 'Entry posted. <a href="post_view.php?id='.mysql_insert_id().'">View</a>';
    else
        echo mysql_error();
}
?>

<form method="post">
    <table>
        <tr>
            <td><label for="title">Title</label></td>
            <td><input name="title" id="title" /></td>
        </tr>
        <tr>
            <td><label for="body">Body</label></td>
            <td><textarea name="body" id="body"></textarea></td>
        </tr>
        <tr>
            <td></td>
            <td><input type="submit" value="Post" /></td>
        </tr>
    </table>
</form>

At the bottom is a simple form with title and body fields, and a submit button. Pretty self explanatory. If you omit the “action” attribute in the form element it will simply post back to the current page; this is what we want.

The PHP at the top simply checks if anything has been posted, and if it has it inserts it into the “posts” table in our database. It doesn’t check that both “title” and “body” have been properly filled out, nor does it prevent users from entering malicious JavaScript code. If you want to check against these things, do it before you insert the data into the database (line 5). However, in a proper implementation this page would be protected with some sort of user authentication, so you may want to allow it if it’s from a trusted user.

The “if” statement checks if there were any (usually syntax) errors in the query. If there weren’t any, and the post was successfully inserted into the database, it notifies the user and adds a link to view the newly created post. Otherwise, it spits out the error for debugging.

Since we now have a link to post_view.php, we might as well create that page next.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
<?php
// post_view.php
include 'mysql.php';
$result = mysql_safe_query('SELECT * FROM posts WHERE id=%s LIMIT 1', $_GET['id']);

if(!mysql_num_rows($result)) {
    echo 'Post #'.$_GET['id'].' not found';
    exit;
}

$row = mysql_fetch_assoc($result);
echo '<h2>'.$row['title'].'</h2>';
echo '<em>Posted '.date('F j<\s\up>S</\s\up>, Y', $row['date']).'</em><br/>';
echo nl2br($row['body']).'<br/>';
echo '<a href="post_edit.php?id='.$_GET['id'].'">Edit</a> | <a href="post_delete.php?id='.$_GET['id'].'">Delete</a> | <a href="index.php">View All</a>';

echo '<hr/>';
$result = mysql_safe_query('SELECT * FROM comments WHERE post_id=%s ORDER BY date ASC', $_GET['id']);
echo '<ol id="comments">';
while($row = mysql_fetch_assoc($result)) {
    echo '<li id="post-'.$row['id'].'">';
    echo (empty($row['website'])?'<strong>'.$row['name'].'</strong>':'<a href="'.$row['website'].'" target="_blank">'.$row['name'].'</a>');
    echo ' (<a href="comment_delete.php?id='.$row['id'].'&post='.$_GET['id'].'">Delete</a>)<br/>';
    echo '<small>'.date('j-M-Y g:ia', $row['date']).'</small><br/>';
    echo nl2br($row['content']);
    echo '</li>';
}
echo '</ol>';

echo <<<HTML
<form method="post" action="comment_add.php?id={$_GET['id']}">
    <table>
        <tr>
            <td><label for="name">Name:</label></td>
            <td><input name="name" id="name" value="{$_COOKIE['name']}"/></td>
        </tr>
        <tr>
            <td><label for="email">Email:</label></td>
            <td><input name="email" id="email" value="{$_COOKIE['email']}"/></td>
        </tr>
        <tr>
            <td><label for="website">Website:</label></td>
            <td><input name="website" id="website" value="{$_COOKIE['website']}"/></td>
        </tr>
        <tr>
            <td><label for="content">Comments:</label></td>
            <td><textarea name="content" id="content"></textarea></td>
        </tr>
        <tr>
            <td></td>
            <td><input type="submit" value="Post Comment"/></td>
        </tr>
    </table>
</form>
HTML
;

This one’s a little more complicated. It displays the blog entry, all comments on the entry, and adds a form to post new comments.

Line 4 gets the post given by the “id” variable in the URL. We use “LIMIT 1” to let MySQL know we only want 1 result back, and it can stop searching after it has found it. Lines 6-9 check that we actually got a result back. Lines 11-15 will display the post. Notice how in the date script I used <sup> to display the suffix on the date – we need to escape the “s” and the “u” so that the date function doesn’t convert them, but the “p” isn’t an accepted letter so it doesn’t need to be escaped. On line 14 we convert new lines into <br/>s so that you don’t need to type them in your post. Line 15 prints out some options we’ll talk about later.

Lines 17-28 will display all the comments in an ordered list. The while loop on line 20 is used to iterate over all the results/comments. We give each comment an “id” so that we can use anchor links, as you will see later.

Lines 30-55 print uses heredoc syntax to print out the form. Heredocs behave the same as double-quoted strings, meaning you can use variables in them. Just remember to use squiggly brackets {} around array variables. We include $_COOKIE variables to automatically fill out the user’s name, email and website so that he doesn’t have to retype them every time he leaves a comment. I will show you how to set these next. Note that if the variable does not exist/isn’t set, the value will just be left blank, like we want. We also include the post ID in the action URL on line 31. We could have just as easily included this in a hidden form field, as many people like to do, but I like to put it in the URL for a little more consistency. If we later decide we want to put the comment form on the actual comment_add page, then we can link directly to it with the id in the URL, whereas otherwise we would have to somehow post the id over.

Let’s add the comment_add.php page next.

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
// comment_add.php
include 'mysql.php';

$expire = time()+60*60*24*30;
setcookie('name', $_POST['name'], $expire, '/');
setcookie('email', $_POST['email'], $expire, '/');
setcookie('website', $_POST['website'], $expire, '/');

mysql_safe_query('INSERT INTO comments (post_id,name,email,website,content,date) VALUES (%s,%s,%s,%s,%s,%s)',
    $_GET['id'], $_POST['name'], $_POST['email'], $_POST['website'], $_POST['content'], time());
mysql_safe_query('UPDATE posts SET num_comments=num_comments+1 WHERE id=%s LIMIT 1', $_GET['id']);
redirect('post_view.php?id='.$_GET['id'].'#post-'.mysql_insert_id());

First we save the user’s input to some cookie variables. We set to expiry date to one month from now. This will be refreshed every time the user comments on something. Lines 9 and 10 simply insert the comment into the database. Line 11 increments the comment counter on the post. We could have just retrieved the number of comments with MySQL’s COUNT(*) command, but this puts extra strain on the database if it’s used a lot. It probably won’t make a difference until you’re getting thousands of visitors per day, but it’s always a good idea to do it right the first time around. Line 12 redirects back the post we were just on, and scrolls the page down to the comment we just inserted – people like to see that their comment was actually posted.

Deleting comments is pretty easy, so we’ll do that next.

<?php
// comment_delete.php
include 'mysql.php';
mysql_safe_query('DELETE FROM comments WHERE id=%s LIMIT 1', $_GET['id']);
mysql_safe_query('UPDATE posts SET num_comments=num_comments-1 WHERE id=%s LIMIT 1', $_GET['post']);
redirect('post_view.php?id='.$_GET['post']);

Not much needs explaining here. Same deal as before. Just remember the decrement the comment counter or it will be off.

Let’s delete some posts too while we’re at it.

<?php
// post_delete.php
include 'mysql.php';
mysql_safe_query('DELETE FROM posts WHERE id=%s LIMIT 1', $_GET['id']);
mysql_safe_query('DELETE FROM comments WHERE post_id=%s', $_GET['id']);
redirect('index.php');

You may have noticed that I haven’t been using closing PHP tags – you don’t need them, and it’s actually probably a good idea not to include them so that you don’t accidentally output some whitespace. Notice we also delete all the comments associated with the post. It won’t hurt much to leave them there, but we might as well save some disk space and give MySQL less to dig through.

On to post_edit.php. I always find the edit pages to be the biggest pain to write.

<?php
// post_edit.php
include 'mysql.php';

if(!empty($_POST)) {
    if(mysql_safe_query('UPDATE posts SET title=%s, body=%s, date=%s WHERE id=%s', $_POST['title'], $_POST['body'], time(), $_GET['id']))
        redirect('post_view.php?id='.$_GET['id']);
    else
        echo mysql_error();
}

$result = mysql_safe_query('SELECT * FROM posts WHERE id=%s', $_GET['id']);

if(!mysql_num_rows($result)) {
    echo 'Post #'.$_GET['id'].' not found';
    exit;
}

$row = mysql_fetch_assoc($result);

echo <<<HTML
<form method="post">
    <table>
        <tr>
            <td><label for="title">Title</label></td>
            <td><input name="title" id="title" value="{$row['title']}" /></td>
        </tr>
        <tr>
            <td><label for="body">Body</label></td>
            <td><textarea name="body" id="body">{$row['body']}</textarea></td>
        </tr>
        <tr>
            <td></td>
            <td><input type="submit" value="Save"/></td>
        </tr>
    </table>
</form>
HTML
;

This one’s not so bad. We simply retrieve the post, and then fill the form with it. We use MySQL’s UPDATE command to update the post, then redirect back to the post so the user can see his changes. You might notice that this page is quite similar to the post_add.php page. You can merge these two pages if you like, so that if you add any more fields to the post, you don’t have to edit two pages, but then you end up with a lot of conditionals just to change a bit of wording and it can get quite messy.

Now, back to the beginning, let’s write the index page. The first page your users will see when the view your blog. It should probably display only the last few entries, but for simplicity, we’re going to make it display all entries on one page.

<?php
// index.php
include 'mysql.php';

echo '<h1>My First Blog</h1>';
echo "<em>Not just another WordPress web log</em><hr/>";

$result = mysql_safe_query('SELECT * FROM posts ORDER BY date DESC');

if(!mysql_num_rows($result)) {
    echo 'No posts yet.';
} else {
    while($row = mysql_fetch_assoc($result)) {
        echo '<h2>'.$row['title'].'</h2>';
        $body = substr($row['body'], 0, 300);
        echo nl2br($body).'...<br/>';
        echo '<a href="post_view.php?id='.$row['id'].'">Read More</a> | ';
        echo '<a href="post_view.php?id='.$row['id'].'#comments">'.$row['num_comments'].' comments</a>';   
        echo '<hr/>';
    }
}

echo <<<HTML
<a href="post_add.php">+ New Post</a>
HTML
;

The loop here is quite similar to the comment loop you saw earlier, so there isn’t much to explain. I’ve shortened the posts to 300 characters so that you don’t end up with too much text on one page. Note that you need a line break after the heredoc if it’s at the end of the page, otherwise PHP will complain.

You should now have a fully functional blog! We will add more features to it in the future, so stay tuned. Please leave some comments below and let me know if this tutorial was too difficult to follow, or too wordy 🙂

download-button

It’s better if you work through the tutorial on your own, but for convenience, I’ve included a zip file with all the above files. I wouldn’t use this on a live site! It’s meant for learning purposes only.

XHTML 1.0 Strict Template

Just a little template I like to use every time I create a new HTML page. The DOCTYPE does make a difference as to how the page is rendered.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title>Untitled</title>
    <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1" />
    <meta name="Keywords" content="" />
    <meta name="Description" content="" />
    <link rel="shortcut icon" type="image/x-icon" href="/favicon.ico" />
    <link rel="stylesheet" type="text/css" href="/style.css" />
</head>
<body>

</body>
</html>

Edit: And here’s a 1.1 template. Ready for Django flatpages, but easily modifyable.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
    <title>{{ flatpage.title }}</title>
        <link rel="stylesheet" type="text/css" href="/media/css/style.css" />
</head>
<body>
    <h1>{{ flatpage.title }}</h1>
    {{ flatpage.content }}
</body>
</html>

How To Create a Database and Install WordPress

This tutorial isn’t just about installing WordPress. It will cover how to set up a database using cpanel, and optionally, how to unzip files directly on your web server.

WordPress has documentation on their site about installation too, but I’m going to share with you how I like to do it.

The Easy Way

Estimated Time: 15 minutes
Skill Level Required: Beginner
Requirements:

  • Access to a web server (FTP and cpanel)
  • An FTP client

Steps:

  1. Download WordPress. Go to wordpress.org/download and click Download WordPress. Save the zip file to somewhere on your computer. The desktop works well (you can delete it in a minute).
  2. Unzip it. Find the file on your computer, right-click it and click “extract here”. If you don’t have an option like that, you might need a file (un)archiver. I like WinRar, but 7-Zip is free. This will create a folder called “wordpress” on your desktop.
  3. Upload it. Open your FTP client (FileZilla is free if you need one), and connect to your web server. You will need a server address (your domain), a username, and password to do this. After you’ve connected, upload all the files inside the WordPress folder (don’t upload the folder itself unless you want WordPress to be located at yoursite.com/wordpress). You probably want to put these files inside the “www” or “public_html” folder on your web server. If there are a bunch of files in there already, they might conflict with WordPress; consider deleting them or moving them to a subfolder.
    Note:
    I’m not responsible if your web server explodes or anything else bad happens.
  4. Create a configuration file. Go to yoursite.com. You should be presented with a screen that looks something like this (from WordPress 2.7)
    wp-create-config-fileIgnore the warning and click the big round “Create a Configuration File” button. It should work for most setups (if it doesn’t, you’d better follow their instructions, not mine).
  5. Next. Ignore the pretty text on the next page, and click “Let’s go!”
    wp-lets-go
  6. Setup the database. Now it’s going to ask you for a bunch of info. If you don’t know what to put here, I can tell you how to set it up if your web host is using cpanel. Open yoursite.com/cpanel in a new tab/window. If you get a 404 or something like that, talk to someone else 🙂 If it asks you for your username and password, enter it. Depending on the version of cpanel, you should be presented with a bunch of icons. Find the one that says “MySQL Databases” and click it. We’re going to create a new database for WordPress so that we don’t accidentally muck anything else up. Near the top of the page there should an area to “create a new database”. Enter something clever like “wordpress” or “mysite” and create the database.
    cpanel-new-database
    Scroll down to where it says “add new user”. Choose a username and password, and create a user. You might as well make your password a bunch of random upper and lowercase letters, numbers, and symbols. You should only need to enter this password once after it’s created. Now, scroll past “add new user” to “add user to database”. Choose the username and database you just created, then click “add”. If the username and database have another username before it, make note of this; you will need to enter these into the WordPress installation form.
    cpanel-add-user
  7. Fill in the WordPress form. Great, your database is set up. Now you can fill in that old form. Enter the database name, user name and password you just chose (with the prefixes if it added them). You shouldn’t need to change the database host or table prefix. Click “Submit”.
    wp-database-connection-details
  8. You’re done. You can listen to WordPress now 🙂 It will guide you through the rest.

The Faster Way

WordPress 2.7 is 1.76 MB compressed, or 5.38 MB uncompressed with 603 files. I find it considerably faster to upload just the zip file.

Estimated Time: 10 minutes
Skill Level Required: Novice
Requirements:

  • Access to a web server (FTP, cpanel, and shell)
  • An FTP client

Assumptions:

  • You can follow the steps above
  • You’re on a linux server, but have no knowledge of linux

Steps:

  1. Download WordPress.
  2. Don’t unzip it.
  3. Upload the zip file.
  4. Open up PuTTY or your favorite SSH program.
  5. Connect to your web server. If you’re on HostGator like me, you can find the IP Address on the left sidebar of your cpanel, under “Account Information”. The Port is 2222. You may need to ask Tech Support to enable Jailed SSH for you though.
  6. Enter your username and password. If you’re new to this, your password won’t appear as you type (no asterisks, nothing).
  7. Type the following commands. You can use tab to auto-complete folder and filenames. Adjust the file/folder names below as needed. Use the “ls” (list directory contents) and “pwd” (print working directory) commands to figure out where you are, if you get lost. Also, “man <command>” will give you more information about a particular command.
    cd public_html/<folder-where-you-uploaded-wordpress>
    unzip wordpress-2.7.zip
    mv wordpress/* .
    rm wordpress-2.7.zip
    rmdir wordpress
  8. Follow steps 4 onward, above.

That’s it for this tutorial, I hope you enjoyed it!

Cut out an Image w/ Lasso Tool, Layer Masks and Gaussian Blur

There are many ways to remove the background from an image in Photoshop. Today, I am going to show you my favorite method: using the lasso tool, layer masks, and a gaussian blur to soften the edges. This method probably isn’t the best for dealing with fuzzy edges like hair; I’ll cover that in another tutorial.

Estimated Time: 10 minutes
Skill Level Required: Absolute beginner

I will be assuming you’re using a PC/Windows; if you’re using a Mac, I believe you can substitute “Ctrl” for “Option”. I will be teaching you lots of hotkeys which can be a big time-saver in the long run.

Before/After

Before and after images with obligatory ocean beach
Before and after images with obligatory ocean beach

Step 1 – open your image

Open your image in Photoshop. I’ll be using this image of Root Beer flavored vodka in Photoshop CS3.

rock-star-root-beer

Step 2 – change background to normal layer

If the layer says Background (in italics), double click it and just “OK” in the dialog that pops up. Otherwise, you won’t have a transparent background.

dbl-click-background

Step 3 – add a vector mask

Click the Add Vector Mask button under the layers panel. This will allow you to hide parts of the image, but bring them back if you subtract too much (whereas the eraser tool is permanent).

add-vector-mask

Step 4 – reset colors

Press D to reset foreground/background colors to black and white, respectively. This will be important when we start painting on the vector mask. Black means that part of the image is hidden/transparent, white means it’s opaque, and grays represent various degrees of transparency.

Step 5 – select polygonal lasso tool

Select the Polygonal Lasso Tool (L). You may need to click and hold the regular Lasso Tool (near the top of the toolbox) to activate a pop-up menu.

Step 6 – click the layer mask

Click on the layer mask you added earlier. It’s the (black and) white image preview on Layer 0. You don’t want to be painting over your image, just the mask!

Step 7 – start cutting away the image

Zoom in (Ctrl =) a bit if you need to, then use the the polygonal tool to start cutting away small pieces of the image. Click somewhere on edge of your object, then add a few more points, tracing its border. After you’ve added about 10 points, or reach the end of the window, click somewhere away from your object, then double-click to close off your polygon (as shown below).

cut-away-small-pieces

Press Alt Backspace to fill this area in with the foreground color (which should be black – if not, you’ve missed a step, press D). Ctrl Backspace will fill with the background color. Part of your image should now be transparent. Press Ctrl D to get rid of your selection, and repeat the process until you’ve cut out your object.

If you’re trying to cut out a really simple object, it might actually be easier just to select the whole object in one go, then press Ctrl Shift I to invert your selection, then press Alt Backspace and Ctrl D like usual. The downside to this method is that if you misclick, it can be hard to correct (you can’t undo a single point).

Don’t worry about making your selections perfect! I will be teaching you how to touch it up the next step.

cut-out-beer

Step 8 – clean up

Select the Brush Tool (B), and set its hardness near 100%.

brush-settings

Make sure you’re still on the vector mask, and your foreground color is black. Zoom in to the edges of your object, and start painting away the rough bits. If you cut away too much, you can press Ctrl Shift Z to “Step Background”. Photoshop only allows one undo, so repeatedly pressly Ctrl Z won’t do you much good. Alt Ctrl Z is Step Forward. You can also press X to swap your foreground and background colors, and then use white to paint back some of your image.

Step 9 – round the corners, and crisp the edges

If the edges of your object are too crisp, too blurry, the corners are too sharp, or you can still see some background around your object, there’s a way to fix that!

First, click the little chain icon on Layer 0, between the preview and layer mask. This will unlink them so that when we apply a blur to the mask, it won’t affect the image. Make sure the mask is still selected. Ctrl Click the mask to turn it into a selection.

If you can still see some background around your object, go Select > Modify > Contract and choose a small value. Usually 2 or 3 pixels works well, but it depends on how much background you left behind! Press Ctrl Shift I to invert your selection, and fill it with black. This (sub)step can also be compensated for in the next one.

Make sure the vector mask is still highlighted, and you have no selections. Go to Filter > Blur > Gaussian Blur. Depending on the size of your image, and how round you want the corners to be, you may want to use different values, but generally about 5 pixels works well. Don’t worry about how blurry it looks, we’re going to fix that. Press Ctrl L to bring up the Levels dialog. You will see a graph with some sliders under Input Levels, and a black to white gradient Output Levels. Grab the left-most slider under Input Levels and drag it near the middle, then grab the right-most slider and also drag it near the middle. Make sure Preview is checked so that you can see what’s going on. Move them around until you have nice smooth edges around your object. If you want your corners more rounded, you might want to go back and use a bigger blur.

levels

That’s it! You’re done. I hope you learned a thing or two from this tutorial, if only a few hotkeys. Leave a comment below if you liked it. Your comments really mean a lot and will encourage me to write more for you in the future.