Getting image data with plain PHP

If you need to find out if a file is a JPEG, PNG or GIF image and you also need the width and height of the image, but for whatever reason you do not have access to GD or ImageMagick or any other external tools or plugins, you can easily read the files with plain PHP.

JPEG

We can consider a file to be a JPEG image if it's encapsulated in a JFIF file format. Unfortunately though, the JFIF standard is not always used as specified, so it would be best if we look for a "Start of Image" marker instead.

/**
 * Returns true if the supplied file is a JPEG image.
 *
 * @param string $filename path to the file
 * @return bool
 */
function isJPEG($filename) {
    $fh = fopen($filename, 'rb');
    $buffer = fread($fh, 2);
    fclose($fh);
    return $buffer === "\xff\xd8";
}

The JPEG width and height can be found after the last SOF0 marker. To do that, we can use preg_match_all(), which is not optimal, but is faster than reading the file byte by byte. The optimal way would be to seek to the correct location, but that is not covered in this article.

/**
 * Returns the width and height of the supplied JPEG image.
 *
 * @param string $filename path to the JPEG image
 * @return array array(width, height)
 */
function getJPEGSize($filename) {
    preg_match_all("/\xff\xc0...(..)(..)/", file_get_contents($filename), $matches);
    $last = count($matches[2]) - 1;
    $height = unpack('n', $matches[1][$last]);
    $width = unpack('n', $matches[2][$last]);
    return array($width[1], $height[1]);
}

PNG

A PNG always starts with a specific PNG header.

/**
 * Returns true if the supplied file is a PNG image.
 *
 * @param string $filename path to the file
 * @return bool
 */
function isPNG($filename) {
    $fh = fopen($filename, 'rb');
    $buffer = fread($fh, 8);
    fclose($fh);
    return $buffer === "\x89PNG\x0d\x0a\x1a\x0a";
}

Immediately after the PNG header comes the IHDR critical chunk containig both width and height as big endian unsigned long integers.

/**
 * Returns the width and height of the supplied PNG image.
 *
 * @param string $filename path to the PNG image
 * @return array array(width, height)
 */
function getPNGSize($filename) {
    $fh = fopen($filename, 'rb');
    fseek($fh, 16);
    $size = unpack('N2', fread($fh, 8));
    fclose($fh);
    return array($size[1], $size[2]);
}

GIF

GIF files always have the string GIF89a or GIF87a at the beginning of the file.

/**
 * Returns true if the supplied file is a GIF image.
 *
 * @param string $filename path to the file
 * @return bool
 */
function isGIF($filename) {
    $fh = fopen($filename, 'rb');
    $buffer = fread($fh, 6);
    fclose($fh);
    return $buffer === 'GIF89a' || $buffer === 'GIF87a';
}

Immediately after the GIF signature we have the width and height of the image as little endian unsigned short integers.

/**
 * Returns the width and height of the supplied GIF image.
 *
 * @param string $filename path to the GIF image
 * @return array array(width, height)
 */
function getGIFSize($filename) {
    $fh = fopen($filename, 'rb');
    fseek($fh, 6);
    $size = unpack('v2', fread($fh, 8));
    fclose($fh);
    return array($size[1], $size[2]);
}