{"id":255,"date":"2017-04-07T07:12:13","date_gmt":"2017-04-07T07:12:13","guid":{"rendered":"http:\/\/learningcenter.paratools.com\/?p=255"},"modified":"2022-04-14T14:04:47","modified_gmt":"2022-04-14T14:04:47","slug":"dessiner-en-c-le-format-ppm","status":"publish","type":"post","link":"https:\/\/learningcenter.paratools.com\/?p=255","title":{"rendered":"Dessiner en C : le format PPM"},"content":{"rendered":"<p>L&rsquo;un des probl\u00e8me parall\u00e8le les plus simple est le traitement d&rsquo;image. Afin de permettre la mise en place de ce type d&rsquo;exemple, il faut se donner les moyen de g\u00e9n\u00e9rer des images en C. C&rsquo;est le but de cet article qui va d\u00e9finir une petite biblioth\u00e8que de dessin bas\u00e9e sur le format PPM.<\/p>\n<p><!--more--><\/p>\n<h1>Portable PixMap<\/h1>\n<p>PPM signifie Portable PixMap c&rsquo;est un format d&rsquo;image extr\u00eamement portable vous pouvez en lire plus <a href=\"https:\/\/fr.wikipedia.org\/wiki\/Portable_pixmap\">ICI<\/a>. Nous l&rsquo;avons retenu car il est extr\u00eamement simple. Il permet de g\u00e9n\u00e9rer des images en quelques lignes de C!<\/p>\n<p>Afin de g\u00e9n\u00e9rer ce format de fichier, il faut avant tout en lire les sp\u00e9cifications <a href=\"http:\/\/netpbm.sourceforge.net\/doc\/ppm.html\">http:\/\/netpbm.sourceforge.net\/doc\/ppm.html<\/a>. En consultant ce document on peut avoir une id\u00e9e plus claire du format:<\/p>\n<blockquote><p>P6<br \/>\nLARGEUR(int)<br \/>\nHAUTEUR(int)<br \/>\nMAXCOULEUR(int)<br \/>\nRASTER DE PIXEL<\/p><\/blockquote>\n<p>Par simplicit\u00e9 nous avons retenu le retour \u00e0 la ligne comme s\u00e9parateur. Le raster de pixel, est tout simplement un tableau de LARGEUR * HAUTEUR \u00e9l\u00e9ments. Le type de l&rsquo;\u00e9l\u00e9ment, comme indiqu\u00e9 dans la norme est un struxture regroupant les trois composantes sur un octet pour un MAXCOULEUR inf\u00e9rieur ou \u00e9gal \u00e0 256, et deux octets pour une valeur maximale de 65536. Nous allons couvir le cas o\u00f9 nous encodons sur un octet, tr\u00e8s classiquement pour une image. Les trois composantes sont Rouge, Vert et Bleu, not\u00e9es R, G et B dans le reste de cet article.<\/p>\n<h1>Formaliser l&rsquo;interface<\/h1>\n<p>En lisant les sp\u00e9cifications nous avons identifi\u00e9 deux objets, des pixels et une image. Cette image aura un ensemble de pixels et les attributs largeur et hauteur.<\/p>\n<h2>Le pixel<\/h2>\n<p>On d\u00e9finit alors un pixel comme une structure C:<\/p>\n<pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\r\nstruct ppm_pixel\r\n{\r\n\tunsigned char r;\r\n\tunsigned char g;\r\n\tunsigned char b;\r\n};\r\n<\/pre>\n<p>On retrouve tr\u00e8s simplement nos trois composantes. Nous ajoutons \u00e9galement une fonction inline pour remplir un pixel. Une fonction quand d\u00e9finie dans le header peut \u00eatre \u00ab\u00a0recopi\u00e9e\u00a0\u00bb dans les diff\u00e9rents fichiers, devenant ainsi plus efficace. Il faut lui ajouter le mot-clef \u00ab\u00a0static\u00a0\u00bb pour ne pas cr\u00e9er de conflits de noms, cette fonction \u00e9tant alors d\u00e9finie dans tous les fichiers o\u00f9 le header a \u00e9t\u00e9 inclus. Pour le pixel d\u00e9finissons un setter:<\/p>\n<pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\r\nstatic inline void ppm_setpixel( struct ppm_pixel * px, unsigned char r , unsigned char g , unsigned char b)\r\n{\r\n\tpx-&gt;r = r;\r\n\tpx-&gt;g = g;\r\n\tpx-&gt;b = b;\r\n}\r\n<\/pre>\n<p>Cette fonction est extr\u00eamement simple, on place simplement les param\u00e8tres dans les membres de la structure.<\/p>\n<h2>L&rsquo;image<\/h2>\n<p>Ensuite d\u00e9finissons une image:<\/p>\n<pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\r\nstruct ppm_image\r\n{\r\n\tunsigned int width;\r\n\tunsigned int height;\r\n\tstruct ppm_pixel * px;\r\n};\r\n<\/pre>\n<p>Simple \u00e9galement, une largeur (width), une hauteur (height) et des pixels. Maintenant d\u00e9finissons les fonctions qui seront le point d&rsquo;entr\u00e9e de l&rsquo;utilisateur:<\/p>\n<pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\r\nint ppm_image_init( struct ppm_image *im , int w , int h );\r\nint ppm_image_release( struct ppm_image *im );\r\nvoid ppm_image_setpixel( struct ppm_image * im, int x, int y, unsigned char r , unsigned char g , unsigned char b);\r\nint ppm_image_dump( struct ppm_image *im, char * path );\r\n<\/pre>\n<p>Nous allons maintenant les couvrir une par une.<\/p>\n<h3>ppm_image_init<\/h3>\n<p>Cette fonction doit instancier une image PPM, en particulier il s&rsquo;agit de proc\u00e9der \u00e0 l&rsquo;allocation m\u00e9moire de cette image pour la rendre manipulable par les primitives de dessin. Elle a \u00e9t\u00e9 impl\u00e9ment\u00e9e comme suit:<\/p>\n<pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\r\nint ppm_image_init( struct ppm_image *im , int w , int h )\r\n{\r\n\tmemset( im, 0, sizeof( struct ppm_image));\r\n\r\n\tim-&gt;width = w;\r\n\tim-&gt;height = h;\r\n\r\n\tim-&gt;px = calloc( sizeof(struct ppm_pixel), w*h );\r\n\r\n\tif( !im-&gt;px)\r\n\t{\r\n\t\tperror(&quot;malloc&quot;);\r\n\t\treturn 1;\r\n\t}\r\n\r\n\treturn 0;\r\n}\r\n<\/pre>\n<p>On vide le contenu de la structure, puis on sauve largeur et hauteur dans la structure. Enfin on utilise la fonction calloc pour allouer de la m\u00e9moire constituant notre raster de pixel. Calloc \u00e9tant une mani\u00e8re optimis\u00e9e d&rsquo;allouer de la m\u00e9moire \u00e0 \u00ab\u00a00\u00a0\u00bb. (pas de malloc + memset!). Et voil\u00e0 la base de l&rsquo;image est pr\u00eate.<\/p>\n<h3>ppm_image_release<\/h3>\n<p>Ici on veut simplement lib\u00e9rer une image, tout en rendant la m\u00e9moire associ\u00e9e au syst\u00e8me.<\/p>\n<pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\r\nint ppm_image_release( struct ppm_image *im )\r\n{\r\n\tif( im == NULL )\r\n\t\treturn 1;\r\n\r\n\tfree( im-&gt;px );\r\n\tim-&gt;px = NULL;\r\n\r\n\tim-&gt;width = 0;\r\n\tim-&gt;height = 0;\r\n\r\n\treturn 0;\r\n}\r\n<\/pre>\n<p>On prends le temps de tout mettre \u00e0 z\u00e9ro dans la structure. Ensuite on fait \u00ab\u00a0free\u00a0\u00bb de la m\u00e9moire. Il est important de mettre le pointeur \u00e0 NULL. En effet free ne produit pas d&rsquo;erreur si on lui passe un pointeur NULL. Ainsi vous couvrez le cas o\u00f9 une image est lib\u00e9r\u00e9e (par erreur) deux fois sans crasher.<\/p>\n<h3>ppm_image_dump<\/h3>\n<p>Cette fonction va se charger d&rsquo;\u00e9crire notre fichier au format PPM. Le processus est assez simple, il suffit de se reporter au standard.<\/p>\n<pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\r\nint ppm_image_dump( struct ppm_image *im, char * path )\r\n{\r\n\tFILE * out = fopen( path , &quot;w&quot;);\r\n\r\n\tif( !out )\r\n\t{\r\n\t\tperror(&quot;fopen&quot;);\r\n\t\treturn 1;\r\n\t}\r\n\r\n\tfprintf(out, &quot;P6\\n&quot;);\r\n\tfprintf(out, &quot;%d\\n&quot;, im-&gt;width);\r\n\tfprintf(out, &quot;%d\\n&quot;, im-&gt;height);\r\n\tfprintf(out, &quot;255\\n&quot;);\r\n\tfwrite( im-&gt;px, sizeof(struct ppm_pixel) , im-&gt;width * im-&gt;height, out );\r\n\r\n\tfclose( out );\r\n\r\n\treturn 0;\r\n}\r\n<\/pre>\n<p>Ici nous ouvrons tout d&rsquo;abord le fichier \u00ab\u00a0path\u00a0\u00bb en \u00e9criture. Ensuite, nous \u00e9crivons le \u00ab\u00a0magic number\u00a0\u00bb de PPM, le P6. Puis les largeurs et hauteurs. Enfin apr\u00e8s avoir donn\u00e9 la valeur maximum de 255 suivie d&rsquo;un retour chariot, nous \u00e9crivons le raster de pixel directment en binaire. Et voil\u00e0, l&rsquo;image est sauvegard\u00e9e.<\/p>\n<h3>ppm_image_setpixel<\/h3>\n<p>Il faut bien s\u00fbr une fonction pour dessiner un pixel. Cette fonction \u00e9tant un accesseur soumis \u00e0 des contraintes de performances (de nombreux pixels modifi\u00e9s par cet appel de base), nous le placerons dans le header en static inline. Le code est le suivant:<\/p>\n<pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\r\nstatic inline void ppm_image_setpixel( struct ppm_image * im, int x, int y, unsigned char r , unsigned char g , unsigned char b)\r\n{\r\n\tstruct ppm_pixel * px = im-&gt;px + im-&gt;width * y + x;\r\n\tppm_setpixel( px, r, g, b);\r\n}\r\n<\/pre>\n<p>La seule complexit\u00e9 est le calcul de l&rsquo;indice du pixel (tableau 2D) dans un tableau C issu d&rsquo;une allocation lin\u00e9aire. Le principe est de se d\u00e9placer selon Y en blocs de la largeur et ensuite d&rsquo;indicer ce d\u00e9placement par la position en X. Ensuite, nous appelons le setter de pixel qui est \u00e9galement static inline.<\/p>\n<h1>Testons le tout<\/h1>\n<p>Il faut maintenant tester le tout en g\u00e9n\u00e9rant une image de test. Mais tout d&rsquo;abord il faut compiler notre biblioth\u00e8que. Nous allons faire un makefile simple.<\/p>\n<pre class=\"brush: bash; title: ; notranslate\" title=\"\">\r\nCC=gcc\r\nCFLAGS=-O3 -g\r\n\r\nlibppm.so : ppm.c\r\n\t$(CC) $(CFLAGS)  -fpic -shared $^ -o $@\r\n<\/pre>\n<p>D\u00e9finissons tout d&rsquo;abord une cible pour notre biblioth\u00e8que PPM. Notez que toute commande apr\u00e8s une cible <b>doit<\/b> \u00eatre indend\u00e9e avec une tabulation. Pour compiler on passe \u00ab\u00a0-fpic\u00a0\u00bb qui compile le code de mani\u00e8re position ind\u00e9pendante (indispensable pour faire une biblioth\u00e8que dynamique). Ensuite \u00ab\u00a0-shared\u00a0\u00bb pour construire un .so. Puis \u00ab\u00a0$^\u00a0\u00bb renvoie aux d\u00e9pendances apr\u00e8s le \u00ab\u00a0:\u00a0\u00bb de la cible. Enfin, \u00ab\u00a0$@\u00a0\u00bb est le nom de la cible, construisant finalement \u00ab\u00a0libppm.so\u00a0\u00bb.<\/p>\n<p>Maintenant \u00e9crivons un petit fichier test (main.c):<\/p>\n<pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\r\n#include &quot;ppm.h&quot;\r\n\r\n\r\nint main(int argc, char *argv[])\r\n{\r\n\tstruct ppm_image im;\r\n\r\n\tppm_image_init( &amp;im, 1024, 1024 );\r\n\r\n\tint i,j;\r\n\r\n\tfor (i = 0; i &lt; 1024; ++i) {\r\n\t\tfor (j = 0; j &lt; 1024; ++j) {\r\n\t\t\tppm_image_setpixel( &amp;im, i, j, i%255, j%255, (i+j)%255);\r\n\t\t}\r\n\t}\r\n\r\n\tfor (i = 0; i &lt; 1024; ++i) {\r\n\t\tppm_image_setpixel( &amp;im, i, i, 255, 0, 0 );\r\n\t}\r\n\r\n\tppm_image_dump( &amp;im , &quot;test.ppm&quot;);\r\n\r\n\tppm_image_release( &amp;im );\r\n\r\n\r\n\treturn 0;\r\n}\r\n<\/pre>\n<p>Ce code cr\u00e9e une image de 1024&#215;1024, dessine des formes color\u00e9es et enfin une diagonale en rouge. Ce dernier test permet de v\u00e9rifier la signification des X et Y que nous avons d\u00e9finis tout \u00e0 l&rsquo;heure. (0,0) \u00e9tant en haut \u00e0 gauche.<\/p>\n<p>Ajoutons maintenant la cible dans le Makefile:<\/p>\n<pre class=\"brush: bash; title: ; notranslate\" title=\"\">\r\nTARGET=test\r\n\r\nall: $(TARGET)\r\n\r\ntest: main.c libppm.so\r\n\t$(CC) $(CFLAGS) $(LDFLAGS) -lppm -L. main.c -o $@\r\n<\/pre>\n<p>Ici nous d\u00e9finissons la cible \u00ab\u00a0all\u00a0\u00bb qui est celle appel\u00e9e par d\u00e9faut (quand on appelle make). Cette cible d\u00e9pends de TARGET qui contient la cible \u00ab\u00a0test\u00a0\u00bb. All appelle donc test. Test d\u00e9pends de main.c (elle est appel\u00e9e si le fichier est modifi\u00e9) et de libpp.so. Ansi libppm.so est construite pour construire test. Enfin, dans la ligne de commande, nous ajoutons \u00ab\u00a0-lppm\u00a0\u00bb (notez que c&rsquo;est la fin du nom de libppm, par standard) et \u00ab\u00a0-L.\u00a0\u00bb pour dire \u00e0 GCC de chercher les librairies dans le r\u00e9pertoire courant \u00ab\u00a0.\u00a0\u00bb.<\/p>\n<p>Maintenant si nous faisons Make:<\/p>\n<pre class=\"brush: bash; title: ; notranslate\" title=\"\">\r\nmake\r\ngcc -O3 -g  -fpic -shared ppm.c -o libppm.so\r\ngcc -O3 -g  -lppm -L. main.c -o test\r\n<\/pre>\n<p>Ajoutons une cible \u00ab\u00a0clean\u00a0\u00bb:<\/p>\n<pre class=\"brush: bash; title: ; notranslate\" title=\"\">\r\nclean:\r\n\trm -fr $(TARGET) *.so\r\n<\/pre>\n<p>Et voil\u00e0 le makefile est complet.<\/p>\n<p>Maintenant si on lance le test, on a une erreur :<\/p>\n<blockquote><p>.\/test: error while loading shared libraries: libppm.so: cannot open shared object file: No such file or directory<\/p><\/blockquote>\n<p>Cela est d\u00fb au fait que nous n&rsquo;avons pas inform\u00e9 le loader de la position du binaire. Il faut l&rsquo;ajouter au LD_LIBRARY_PATH. On peut v\u00e9rifier d&rsquo;o\u00f9 viennent les biblioth\u00e8ques avec \u00ab\u00a0ldd\u00a0\u00bb:<\/p>\n<pre class=\"brush: bash; title: ; notranslate\" title=\"\">\r\nldd .\/test                                                          \r\n\tlinux-vdso.so.1 =&gt;  (0x00007ffdc674c000)\r\n\tlibppm.so =&gt; not found\r\n\tlibc.so.6 =&gt; \/lib64\/libc.so.6 (0x00007f35861de000)\r\n\t\/lib64\/ld-linux-x86-64.so.2 (0x00007f35865b9000)\r\n<\/pre>\n<p>Ajoutons le dossier courant \u00e0 LD_LIBRARY_PATH:<\/p>\n<pre class=\"brush: bash; title: ; notranslate\" title=\"\">\r\nexport LD_LIBRARY_PATH=$PWD:$LD_LIBRARY_PATH\r\n<\/pre>\n<p>Rev\u00e9rifions ldd:<\/p>\n<pre class=\"brush: bash; title: ; notranslate\" title=\"\">\r\nldd .\/test\r\n\tlinux-vdso.so.1 =&gt;  (0x00007ffc0dd16000)\r\n\tlibppm.so =&gt; \/home\/jbbesnard\/ppm\/libppm.so (0x00007f15a8d89000)\r\n\tlibc.so.6 =&gt; \/lib64\/libc.so.6 (0x00007f15a89af000)\r\n\t\/lib64\/ld-linux-x86-64.so.2 (0x00007f15a8f8c000)\r\n<\/pre>\n<p>Et voil\u00e0, le programme peut s&rsquo;\u00e9x\u00e9cuter. Vous obtiendrez alors une image PPM dans le dossier courant. Vous avez maintenant une biblioth\u00e8que simple de dessin \u00e0 votre disposition.<\/p>\n<p><a href=\"http:\/\/learningcenter.paratools.com\/wp-content\/uploads\/2017\/04\/test.png\"><img loading=\"lazy\" src=\"http:\/\/learningcenter.paratools.com\/wp-content\/uploads\/2017\/04\/test.png\" alt=\"\" width=\"1024\" height=\"1024\" class=\"aligncenter size-full wp-image-266\" srcset=\"https:\/\/learningcenter.paratools.com\/wp-content\/uploads\/2017\/04\/test.png 1024w, https:\/\/learningcenter.paratools.com\/wp-content\/uploads\/2017\/04\/test-150x150.png 150w, https:\/\/learningcenter.paratools.com\/wp-content\/uploads\/2017\/04\/test-300x300.png 300w, https:\/\/learningcenter.paratools.com\/wp-content\/uploads\/2017\/04\/test-768x768.png 768w, https:\/\/learningcenter.paratools.com\/wp-content\/uploads\/2017\/04\/test-940x940.png 940w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/a><\/p>\n<h1>Conclusion<\/h1>\n<p>Nous avons vu comment impl\u00e9menter une biblioth\u00e8que de dessin minimale supportant le format PPM. Cette biblioth\u00e8que servira de base \u00e0 des exemples de traitement d&rsquo;image par la suite.<\/p>\n<h1>Listings<\/h1>\n<p>Le code d&rsquo;une biblioth\u00e8que PPM simpliste.<\/p>\n<p>ppm.h:<\/p>\n<pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\r\n#ifndef PPM_H\r\n#define PPM_H\r\n\r\nstruct ppm_pixel\r\n{\r\n\tunsigned char r;\r\n\tunsigned char g;\r\n\tunsigned char b;\r\n};\r\n\r\nstatic inline void ppm_setpixel( struct ppm_pixel * px, unsigned char r , unsigned char g , unsigned char b)\r\n{\r\n\tpx-&gt;r = r;\r\n\tpx-&gt;g = g;\r\n\tpx-&gt;b = b;\r\n}\r\n\r\nstruct ppm_image\r\n{\r\n\tunsigned int width;\r\n\tunsigned int height;\r\n\tstruct ppm_pixel * px;\r\n};\r\n\r\nint ppm_image_init( struct ppm_image *im , int w , int h );\r\nint ppm_image_release( struct ppm_image *im );\r\n\r\nstatic inline void ppm_image_setpixel( struct ppm_image * im, int x, int y, unsigned char r , unsigned char g , unsigned char b)\r\n{\r\n\tstruct ppm_pixel * px = im-&gt;px + im-&gt;width * y + x;\r\n\tppm_setpixel( px, r, g, b);\r\n}\r\n\r\nint ppm_image_dump( struct ppm_image *im, char * path );\r\n\r\n\r\n#endif \/* PPM_H *\/\r\n<\/pre>\n<p>ppm.c:<\/p>\n<pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\r\n#include &quot;ppm.h&quot;\r\n\r\n#include &lt;stdio.h&gt;\r\n#include &lt;stdlib.h&gt;\r\n#include &lt;string.h&gt;\r\n\r\n\r\nint ppm_image_init( struct ppm_image *im , int w , int h )\r\n{\r\n\tmemset( im, 0, sizeof( struct ppm_image));\r\n\r\n\tim-&gt;width = w;\r\n\tim-&gt;height = h;\r\n\r\n\tim-&gt;px = malloc( w * h * sizeof(struct ppm_pixel));\r\n\r\n\tif( !im-&gt;px)\r\n\t{\r\n\t\tperror(&quot;malloc&quot;);\r\n\t\treturn 1;\r\n\t}\r\n\r\n\treturn 0;\r\n}\r\n\r\nint ppm_image_release( struct ppm_image *im )\r\n{\r\n\tif( im == NULL )\r\n\t\treturn 1;\r\n\r\n\tfree( im-&gt;px );\r\n\tim-&gt;px = NULL;\r\n\r\n\tim-&gt;width = 0;\r\n\tim-&gt;height = 0;\r\n\r\n\treturn 0;\r\n}\r\n\r\nint ppm_image_dump( struct ppm_image *im, char * path )\r\n{\r\n\tFILE * out = fopen( path , &quot;w&quot;);\r\n\r\n\tif( !out )\r\n\t{\r\n\t\tperror(&quot;fopen&quot;);\r\n\t\treturn 1;\r\n\t}\r\n\r\n\tfprintf(out, &quot;P6\\n&quot;);\r\n\tfprintf(out, &quot;%d\\n&quot;, im-&gt;width);\r\n\tfprintf(out, &quot;%d\\n&quot;, im-&gt;height);\r\n\tfprintf(out, &quot;255\\n&quot;);\r\n\tfwrite( im-&gt;px, sizeof(struct ppm_pixel) , im-&gt;width * im-&gt;height, out );\r\n\r\n\tfclose( out );\r\n\r\n\treturn 0;\r\n}\r\n<\/pre>\n<p>Makefile:<\/p>\n<pre class=\"brush: bash; title: ; notranslate\" title=\"\">\r\nCC=gcc\r\nCFLAGS=-O3 -g\r\n\r\nTARGET=test\r\n\r\nall: $(TARGET)\r\n\r\nlibppm.so : ppm.c\r\n\t$(CC) $(CFLAGS)  -fpic -shared $^ -o $@\r\n\r\ntest: main.c libppm.so\r\n\t$(CC) $(CFLAGS) $(LDFLAGS) -lppm -L. main.c -o $@\r\n\r\nclean:\r\n\trm -fr $(TARGET) *.so\r\n<\/pre>\n","protected":false},"excerpt":{"rendered":"<p>L&rsquo;un des probl\u00e8me parall\u00e8le les plus simple est le traitement d&rsquo;image. Afin de permettre la mise en place de ce type d&rsquo;exemple, il faut se donner les moyen de g\u00e9n\u00e9rer des images en C. C&rsquo;est le but de cet article qui va d\u00e9finir une petite biblioth\u00e8que de dessin bas\u00e9e sur le format PPM.<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":[],"categories":[12,36],"tags":[37,38,39],"_links":{"self":[{"href":"https:\/\/learningcenter.paratools.com\/index.php?rest_route=\/wp\/v2\/posts\/255"}],"collection":[{"href":"https:\/\/learningcenter.paratools.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/learningcenter.paratools.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/learningcenter.paratools.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/learningcenter.paratools.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=255"}],"version-history":[{"count":13,"href":"https:\/\/learningcenter.paratools.com\/index.php?rest_route=\/wp\/v2\/posts\/255\/revisions"}],"predecessor-version":[{"id":269,"href":"https:\/\/learningcenter.paratools.com\/index.php?rest_route=\/wp\/v2\/posts\/255\/revisions\/269"}],"wp:attachment":[{"href":"https:\/\/learningcenter.paratools.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=255"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/learningcenter.paratools.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=255"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/learningcenter.paratools.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=255"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}