Skip to content

Changing Images in a PDF

The PHP routines to fill in PDF forms work great, and now my partner wants to use them too. Changing the forms to make the physician name a fill-in field rather than fixed text is easy, but what to do about the signature? It's not like a check; a pixelated image would be fine (I'm not worried about someone forging a preschool physical exam note). But it's not that easy to insert an image into an existing PDF file. I don't want to parse the entire PDF and rewrite it.

Fortunately, images are stored as individual objects in the PDF file, so if there is an image I can identify, I can easily replace it with one of my own choosing. The placement and size of the image on the page is part of the page description, so the new image will be in exactly the same place as the old.

So, for example, I grab a blank check image off the web:

Blank Check

And I create a 1 pixel white png image to insert into the signature line. Then I use PDFEscape to create a PDF with those images (scaled to the appropriate size) and some useful text.

Now the plan is to use PHP to replace the white box with a signature image. The problem is programmatically figuring out where in the file is the image.

I don't want to have to parse the whole file, so I use a RegExp to look for a stream object with a width of 1, and assume that it is the image I want to replace. Depending on the PDF, it may be necessary to use a more sophisicated test.

So to replace the image, I open the PDF file, create a PDF image object from the replacement image, and use str_replace to replace it. Simple!.

$images = array(
$image = @imagecreatefrompng(__DIR__.'/'.$images[$_GET['which']].'.png');
$file = file_get_contents(__DIR__.'/blank check.pdf');
$blank = findBlankImage($file);

if ($blank !== FALSE && $image !== FALSE){
	$sig = streamfromimage($image);
	$file = str_replace ($blank, $sig, $file);
header('Content-Type: application/pdf');
//header('Content-disposition: attachment; filename="big check.pdf"'); // Uncomment this to force downloading rather than viewing in browser
die ($file);

function findBlankImage ($file){
	// find a stream with /Width 1 (not a very sensitive search, but it could be made better)
	// Ugly regexp; match '<< /Width 1 >> stream {stuff} endstream'. Suffix means ungreedy, dot matches newline
	$result = preg_match ('#(<<[^>]*/Width\s+1\b[^>]*>>\s*stream\b.*\bendstream\b)#Us', $file, $matches);
	if ($result == 0) return FALSE;
	return $matches[1];

function streamfromimage($im){
	// from
	$h = imagesy($im);
	$w = imagesx($im);
	$stream = '';
	for ($row = 0; $row < $h; ++$row) for ($col = 0; $col < $w; ++$col){
		$colorindex = imagecolorat($im, $col, $row);
		$colors = imagecolorsforindex($im, $colorindex);
		$stream .= sprintf('%c%c%c', $colors['red'], $colors['green'], $colors['blue']);
	$length = strlen($stream);
	return "<</Subtype /Image /Width $w /Height $h /ColorSpace /DeviceRGB /BitsPerComponent 8
	/Length $length >> stream

For the check example, I need some good signatures. The original Macintosh team signed the inside of the case. We can grab a few famous signatures from there.

{ 3 } Comments

  1. R. Seisselberg | July 2, 2017 at 8:32 am | Permalink

    Hello Danny,
    I found by chance this contribution and also immediately tried.
    One thing I do not get however. If I exchange your images and do not use a black / white image but a colored error occurs. It seems as if color information is lost here.
    Can you give me a tip?
    Thank you from Germany,

  2. Danny | July 2, 2017 at 11:31 am | Permalink

    It should work with a colored image. It’s the same code that I’ve used in which handles colored images.

  3. R. Seisselberg | July 3, 2017 at 5:54 am | Permalink

    many thanks for your response.
    There seems to be a mistake with me, I can you
    The file once and you take a look at it?
    I also like to give a small donation :-)

Post a Comment

Your email is never published nor shared. Required fields are marked *