{"id":1731,"date":"2011-04-01T02:10:52","date_gmt":"2011-04-01T08:10:52","guid":{"rendered":"http:\/\/bililite.nfshost.com\/blog\/?p=1731"},"modified":"2011-04-12T12:16:41","modified_gmt":"2011-04-12T18:16:41","slug":"creating-pdfs-with-php-part-4","status":"publish","type":"post","link":"https:\/\/bililite.com\/blog\/2011\/04\/01\/creating-pdfs-with-php-part-4\/","title":{"rendered":"Creating PDFs with PHP, part 4: Images"},"content":{"rendered":"<p>Now that we can draw in our PDF, we want to add images. There are two kinds of images, <a href=\"http:\/\/graphicssoft.about.com\/od\/aboutgraphics\/a\/bitmapvector.htm\">bitmapped and vector<\/a>. In PDF, images are called <code>XObject<\/code>s (The X stands for external, meaning defined outside the page). Vector images are easier, since they are just packages of PDF drawing commands, a sort of macro. PDF calls them <code>Form<\/code>s, since they were originally used to draw the boxes on a printed form.<\/p>\r\n<p><a href=\"\/blog\/blogfiles\/pdf\/pdftest.php?which=3&output=source\">See the code<\/a>.<\/p>\r\n<!--more-->\r\n<h3>Vector graphics<\/h3>\r\n<p>Using an XObject Form involves three parts:<\/p>\r\n<dl>\r\n<dt>Define the image<\/dt>\r\n<dd>Create a content stream with the drawing commands, like:<\/dd>\r\n<dd><pre><code class=\"language-pdf\">6 0 obj\r\n&lt;&lt;\r\n\/Subtype \/Form % defines the type of image\r\n\/Resources 2 0 R % just like the resource dictionary for a page; no reason not to use the same one\r\n\/BBox [0 0 40 40] % the bounding box of the image, as [left bottom right top]. No reason not to use [0 0 width height]\r\n\/Length 47\r\n&gt;&gt;\r\nstream\r\n5 w\r\n0 0 40 40 re % draw a black box\r\n0 0 0 RG S\r\nendstream\r\nendobj<\/code><\/pre><\/dd>\r\n<dt>Define a name for the image<\/dt>\r\n<dd>In the resource dictionary, determine a name for the image (this is arbitrary) in the <code class=\"language-pdf\">\/XObject<\/code> subdictionary:<\/dd>\r\n<dd><pre><code class=\"language-pdf\">2 0 obj\r\n&lt;&lt;\r\n  % ... other resource definitions\r\n  \/XObject &lt;&lt; \r\n    \/Image1 6 0 R % the name of object 6, defined above\r\n    % ... other images\r\n  &gt;&gt;\r\n&gt;&gt;<\/code><\/pre><\/dd>\r\n<dt>Use the image<\/dt>\r\n<dd>In the content stream for the page, include the command<\/dd>\r\n<dd><pre><code class=\"language-pdf\">\/Image1 Do<\/code><\/pre><\/dd>\r\n<\/dl>\r\n<p>The question is obvious: how do you tell PDF <em>where<\/em> on the page to draw the image? The answer is, you don't. The drawing commands draw a box from <code>(0,0)<\/code> to <code>(40,40)<\/code>, and that's where it's drawn. But you object: that makes them worthless!<\/p>\r\n<p>The trick is to redefine the coordinate system so that <code>(0,0)<\/code> ends up where you want. PDF uses <a href=\"http:\/\/en.wikipedia.org\/wiki\/Affine_transformation\">affine transformations<\/a>:<\/p>\r\n<pre><code><em>x?<\/em> = a<em>x<\/em>+c<em>y<\/em>+e\r\n<em>y?<\/em> = b<em>x<\/em>+d<em>y<\/em>+f<\/code><\/pre>\r\n<p>where <em>x<\/em> is the x-coordinate in the new coordinate system (where the image \"thinks\" its drawing) and <em>x?<\/em> is the x-coordinate in the original coordinate system. So to put the image with the lower left corner (its <code>(0,0)<\/code> point) at <code>(50,100)<\/code>, you would want to have <code><em>x?<\/em> = <em>x<\/em>+50<\/code> and <code><em>y?<\/em> = <em>y<\/em>+100<\/code>, or <code>a=1 b=0 c=0 d=1 e=50 f=100<\/code>. The PDF command for that is <code>cm<\/code> (for coordinate matrix), so the drawing command would be:<\/p>\r\n<pre><code class=\"language-pdf\">1 0 0 1 50 100 cm\r\n\/Image1 Do<\/code><\/pre>\r\n<p>Coordinate transformations are cumulative, so repeating the translation for another image would push it too far. You want to reset the transformation matrix before each drawing. You could calculate the inverse matrix (for the translation above, it's easy: <code class=\"language-pdf\">1 0 0 1 -50 -100 cm<\/code>, but other transformations are more complicated) but PDF has an easier way: a graphics state stack. <code>q<\/code> pushes the current graphics state, including the transformation matrix, and <code>Q<\/code> pops it, so the final drawing command would be:<\/p>\r\n<pre><code class=\"language-pdf\">q\r\n1 0 0 1 50 100 cm\r\n\/Image1 Do\r\nQ<\/code><\/pre>\r\n<p>Other transformations are possible, and the work of getting the math right has already been done:<\/p>\r\n<dl>\r\n<dt>Translation of the origin to <code>(<em>x<\/em>,<em>y<\/em>)<\/code><\/dt>\r\n<dd>As above, <code>1 0 0 1 <em>x<\/em> <em>y<\/em> cm<\/code>.<\/dd>\r\n<dt>Rotation around the origin by angle <em>theta<\/em><\/dt>\r\n<dd><code><em>cos(theta)<\/em> <em>sin(theta)<\/em> <em>&ndash;sin(theta)<\/em> <em>cos(theta)<\/em> 0 0 cm<\/code>.<\/dd>\r\n<dt>Scale (change the size of) the x-direction by a factor <em>xscale<\/em> and the y-direction by <em>yscale<\/em><\/dt>\r\n<dd><code><em>xscale<\/em> 0 0 <em>yscale<\/em> 0 0 cm<\/code><\/dd>\r\n<\/dl>\r\n<p>These do the transformation around the origin (and doing them in the above order has the expected results; doing the transforms in some other order means translating in scaled units or along rotated axes, which is rarely what you want). More often, we want to place and rotate around the center of the image, so after the transformation, translate backwards half the height and width (in the new coordinate system): <code>1 0 0 1 <em>-width\/2<\/em> <em>-height\/2<\/em> cm<\/code>.<\/p>\r\n<h3>Bitmapped graphics<\/h3>\r\n<p>Bitmapped images are simply streams of bits, with a given color space (grayscale, <a href=\"http:\/\/en.wikipedia.org\/wiki\/CMYK_color_model\">CMYK<\/a> or RGB) and a given number of bits per color component (grayscale has one component, CMYK has four and RGB has three). The easiest to use is RGB, with 8 bits per component, since that translates directly from the PHP image routines.<\/p>\r\n<p>Using a bitmapped image is exactly the same as for a vector image, except the content stream is different:<\/p>\r\n<pre><code class=\"language-pdf\">7 0 obj\r\n&lt;&lt;\r\n\/Subtype \/Image % defines the type of image\r\n\/Width 50 % no bounding box, just a width and height\r\n\/Height 50\r\n\/ColorSpace \/DeviceRGB\r\n\/BitsPerComponent 8\r\n\/Length 2500\r\n&gt;&gt;\r\nstream\r\n...generate the bytes\r\nendstream\r\nendobj<\/code><\/pre>\r\n<p>And \"generate the bytes\" for a PHP image is straightforward:<\/p>\r\n<pre><code class=\"language-php\">for ($row = 0; $row < $height; ++$row) for ($col = 0; $col < $width; ++$col){\r\n  $colorindex = imagecolorat($im, $col, $row);\r\n  $colors = imagecolorsforindex($im, $colorindex);\r\n  $image-&gt;contents .= sprintf('%c%c%c', $colors['red'], $colors['green'], $colors['blue']);\r\n}<\/code><\/pre>\r\n<p>There is a huge gotcha here: the image is always scaled to be 1 pixel square. I don't know why; the width and height are there in the image definition, but that's the way it is. So you <em>have<\/em> to scale the image with <code><em>width<\/em> 0 0 <em>height<\/em> 0 0 cm<\/code> (or multiply <em>width<\/em> and <em>height<\/em> by some scale factor) before displaying with <code class=\"language-pdf\">\/bitmappedImage1 Do<\/code>. Then, since the units are now scaled, to place and rotate around the center you have to use <code>1 0 0 1 <em>-0.5<\/em> <em>-0.5<\/em> cm<\/code> (half the original (1x1) sizes). Trust me, this works.<\/p>\r\n<p>The <a href=\"\/blog\/blogfiles\/pdf\/pdftest.php?which=3&output=source\">final code<\/a> is pretty simple.<\/p>\r\n<h3>Example<\/h3>\r\n<p><a href=\"\/blog\/blogfiles\/pdf\/pdftest.php?count=1&which=3\">See the example<\/a>.<\/p>\r\n<p>Create the apple vector image:<\/p>\r\n<pre><code class=\"language-php\">$pdf->newForm();\r\n\/\/ http:\/\/en.wikipedia.org\/wiki\/File:Apple_logo_black.svg, same as last example but now going into an Xform\r\n$pdf->moveto(28.70919,92.37034);\r\n$pdf->curveto(32.22477,92.37025,36.46696,91.64368,41.43575,90.19065);\r\n$pdf->curveto(46.45132,88.73743999999999,49.77944,88.01088,51.42013,88.01096);\r\n$pdf->curveto(53.52944,88.01088,56.97475,88.83118999999999,61.756057,90.4719);\r\n$pdf->curveto(66.537237,92.11243,70.685667,92.93275,74.201377,92.93284);\r\n$pdf->curveto(79.966907,92.93275,85.099717,91.38587,89.599807,88.29221);\r\n$pdf->curveto(92.130957,86.51088,94.638767,84.09682000000001,97.123244,81.05002);\r\n$pdf->curveto(93.373147,77.862449,90.630967,75.02649,88.896687,72.54219);\r\n$pdf->curveto(85.755967,68.04213,84.185657,63.07338,84.185747,57.63594);\r\n$pdf->curveto(84.185657,51.682770000000005,85.849717,46.31558,89.177937,41.53438);\r\n$pdf->curveto(92.505957,36.75309,96.302824,33.729659999999996,100.56855,32.46407000000001);\r\n$pdf->curveto(98.787204,26.69842,95.834084,20.674989999999994,91.709187,14.393749999999997);\r\n$pdf->curveto(85.474717,4.971879999999999,79.287227,0.26094000000000506,73.146687,0.26094000000000506);\r\n$pdf->curveto(70.709107,0.26094000000000506,67.334107,1.0343799999999987,63.021687,2.581249999999997);\r\n$pdf->curveto(58.755997,4.128129999999999,55.14663,4.9015600000000035,52.19356,4.901570000000007);\r\n$pdf->curveto(49.24038,4.9015600000000035,45.79507,4.104690000000005,41.85763,2.510940000000005);\r\n$pdf->curveto(37.96696,0.8703200000000066,34.8029,0.05001000000000033,32.36544,0.04999999999999716);\r\n$pdf->curveto(25.00603,0.05001000000000033,17.78729,6.284369999999996,10.70919,18.75313);\r\n$pdf->curveto(3.63105,31.081220000000002,0.09199,43.17496,0.092,55.03438);\r\n$pdf->curveto(0.09199,66.04993999999999,2.7873,75.02649,8.1779402,81.96409);\r\n$pdf->curveto(13.61542,88.9015,20.45916,92.37025,28.70919,92.37034);\r\n$pdf->moveto(73.006057,120.07346);\r\n$pdf->curveto(73.193477,119.46397,73.310667,118.92491,73.357627,118.45627);\r\n$pdf->curveto(73.404417,117.98741,73.427857,117.51866,73.427937,117.05002);\r\n$pdf->curveto(73.427857,114.04991,72.724727,110.76867,71.318557,107.20627);\r\n$pdf->curveto(69.912237,103.64367,67.685677,100.33899,64.638877,97.29221);\r\n$pdf->curveto(62.013807,94.71399,59.412247,92.97962,56.83419,92.08909);\r\n$pdf->curveto(55.1935,91.57337,52.70913,91.17493,49.38106,90.89377);\r\n$pdf->curveto(49.47476,98.01868,51.32632,104.18272999999999,54.93575,109.38596);\r\n$pdf->curveto(58.591927,114.58897,64.615367,118.15147,73.006057,120.07346);\r\n$pdf->fill (0,149,182); \/\/ biondi blue\r\n$pdf->appendForm('apple', 101, 121);<\/code><\/pre>\r\n<p>And if we have these images: <strong>smiley.png<\/strong> <img decoding=\"async\" loading=\"lazy\" alt=\"smiley\" src=\"\/blog\/blogfiles\/pdf\/smiley.png\" title=\"smiley\" class=\"alignnone\" width=\"58\" height=\"59\" \/>and <strong>smiley2.png<\/strong> <img decoding=\"async\" loading=\"lazy\" alt=\"long smiley\" src=\"\/blog\/blogfiles\/pdf\/smiley2.png\" title=\"smiley2\" class=\"alignnone\" width=\"174\" height=\"59\" \/> we can create bitmapped images:<\/p>\r\n<pre><code class=\"language-php\">$im = imagecreatefrompng('smiley.png');\r\n$pdf->appendImage($im);\r\n$im2 = imagecreatefrompng('smiley2.png');\r\n$pdf->appendImage($im2);<\/code><\/pre>\r\n<p>And use the images:<\/p>\r\n<pre><code class=\"language-php\">$pdf->placeimage('apple', 0, 0, 0, 1, 3); \/\/ centered at origin, stretched vertically\r\n$pdf->placeimage('apple', 300, 300); \/\/ image placed straight\r\n$pdf->placeimage('apple', 300, 500, M_PI\/2); \/\/ rotated up 90 degrees\r\n$pdf->placeimage($im, 200, 200); \/\/ image placed straight\r\n$pdf->placeimage($im2, 200, 400, deg2rad(60)); \/\/ rotated 60 degrees<\/code><\/pre>\r\n<h3>Transparency<\/h3>\r\n<p>You will notice there is no mention of the <a href=\"http:\/\/en.wikipedia.org\/wiki\/Alpha_compositing\">alpha channel<\/a> in the images or other forms of transparency. This is a format from 1993, after all. We were lucky to have transparent colors in our GIFs. But it is possible; see the <a href=\"http:\/\/fpdf.org\/en\/script\/script74.php\">transparency<\/a> and <a href=\"http:\/\/fpdf.org\/en\/script\/script83.php\">alpha channel<\/a> extensions to <a href=\"http:\/\/fpdf.org\">FPDF<\/a>. I just am not going to go into it here.<\/p>\r\n<h3>Compression<\/h3>\r\n<p>The classes presented here allow for <a href=\"http:\/\/en.wikipedia.org\/wiki\/Gzip\">gzip<\/a> compression, but there are better ways to compress images. PDF supports <a href=\"http:\/\/en.wikipedia.org\/wiki\/Portable_Network_Graphics\">png<\/a> and <a href=\"http:\/\/en.wikipedia.org\/wiki\/Jpeg\">jpeg<\/a> compressed images, as well as color palettes to reduce the bits per pixel. See the  <a href=\"http:\/\/fpdf.org\">FPDF<\/a> functions <code>Image<\/code>, <code>_parsejpeg<\/code> and <code>_parsepng<\/code> for details.<\/p>\r\n<p><a href=\"\/blog\/2011\/04\/01\/creating-pdfs-with-php-part-5\/\">Continued&hellip;<\/a><\/p>\r\n\r\n\r\n","protected":false},"excerpt":{"rendered":"Now that we can draw in our PDF, we want to add images. There are two kinds of images, bitmapped and vector. In PDF, images are called XObjects (The X stands for external, meaning defined outside the page). Vector images are easier, since they are just packages of PDF drawing commands, a sort of macro. [&hellip;]","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[15,9],"tags":[],"_links":{"self":[{"href":"https:\/\/bililite.com\/blog\/wp-json\/wp\/v2\/posts\/1731"}],"collection":[{"href":"https:\/\/bililite.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/bililite.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/bililite.com\/blog\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/bililite.com\/blog\/wp-json\/wp\/v2\/comments?post=1731"}],"version-history":[{"count":16,"href":"https:\/\/bililite.com\/blog\/wp-json\/wp\/v2\/posts\/1731\/revisions"}],"predecessor-version":[{"id":1794,"href":"https:\/\/bililite.com\/blog\/wp-json\/wp\/v2\/posts\/1731\/revisions\/1794"}],"wp:attachment":[{"href":"https:\/\/bililite.com\/blog\/wp-json\/wp\/v2\/media?parent=1731"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/bililite.com\/blog\/wp-json\/wp\/v2\/categories?post=1731"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/bililite.com\/blog\/wp-json\/wp\/v2\/tags?post=1731"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}