source: spip-zone/_plugins_/filtres_images_vectorise/trunk/filtres/image_geometrize.php @ 116199

Last change on this file since 116199 was 116199, checked in by cedric@…, 21 months ago

Un plugin pour vectoriser en SVG des images bitmap, qui propose 4 nouveaux filtres

  • extraire_palette_couleurs permet d'extraire une palette de couleur d'une image (par defaut les 3 couleurs les plus representees) en utilisant un calcul des couleurs dominantes par partitionnement en k-moyennes

`
<BOUCLE_palette(POUR){tableau #FICHIER|extraire_palette_couleurs{3}}>
<div style="display: inline-block;width: 30px;height: 15px;background-color: #VALEUR;"></div>
</BOUCLE_palette>
`

  • image_geometrize permet de creer une image SVG approchante de l'image d'origine en utilisant le lib GeometrizePHP https://github.com/Cerdic/geometrize-php/ (attention methode gourmande en temps de calcul)
  • image_potrace permet de generer un trace SVG depuis l'image d'origine, a l'aide de Potracio PHP qui est un portage PHP de PotRace? https://seenthis.net/messages/645575
  • image_geopotrize combine les 2 techniques : un background geometrize qui n'a pas besoin d'un grand nombre de shapes et un trace potrace superpose/mixe

Les 3 filtres ont tout un tas d'option pour qui veut jouer avec, mais les reglages par defaut permettent d'avoir immediatement un joli resultat exploitable
A noter que pour image_geometrize, le temps de calcul peut depasser les 30s pour generer le nombre de shapes demandees, selon les reglages utilises.
Dans ce cas le calcul est arrete au bout de 20s, on stocke et on renvoie l'image provisoire incomplete, et on stocke l'etat du calcul qui reprendra au prochain calcul de la page.

(J'evite les screenshots dans le message de commit mais le coeur y est)

File size: 10.0 KB
Line 
1<?php
2/**
3 * Fonctions du plugin Filtres Images Vectorise
4 *
5 * @plugin     Filtres Images Vectorise
6 * @copyright  2019
7 * @author     Cedric
8 * @licence    GNU/GPL
9 * @package    SPIP\Filtres Images Vectorise\Filtres
10 */
11
12use \Cerdic\Geometrize\Shape\ShapeTypes;
13use \Cerdic\Geometrize\Bitmap;
14use \Cerdic\Geometrize\ImageRunner;
15use \Cerdic\Geometrize\Version;
16
17/**
18 * Generation d'une Image SVG a partir d'un bitmap
19 * en utilisant geometrize
20 * https://github.com/Cerdic/geometrize-php/
21 *
22 * @param string $img
23 * @param string|int $nb_shapes
24 *   'auto' pour un nombre de shape calcule automatiquement en fonction de l'image
25 *   ou 'x2.5' pour utiliser le nombre de shape auto * 2.5 (ameliorer la qualite automatique donc)
26 *   ou un nombre de formes a utiliser pour geometrizer l'image
27 * @param array $options
28 *   string shapes : liste des formes utilisables (separee par des virgules) (par defaut triangles)
29 *   int alpha : transparence des formes (de 0 = opaque a 127 = transparente) (par defaut 0)
30 *   string background : couleur de fond pour initialiser l'image (par defaut extraite de l'image)
31 *   int candidateShapesPerStep : nombres de formes aleatoires testees a chaque iteration (par defaut 200)
32 *   int shapeMutationsPerStep : nombres de tentatives de mutation pour ameliorer la forme trouvee (par defaut 85)
33 *   int maxShapes : nombre maximum de shapes si auto est utilise
34 *
35 * @return string
36 * @throws Exception
37 */
38function image_geometrize($img, $nb_shapes='auto', $options = []){
39        static $deja = [];
40
41        // seulement un calcul par image par hit pour ne pas surcharger si la meme image apparait plusieurs fois dans la page
42        // et est trop lourde pour etre calculee en 1 fois
43        if (isset($deja[$img])) {
44                return $deja[$img];
45        }
46
47        $fonction = array('image_geometrize', func_get_args());
48        $opt = json_encode($options);
49        $cache = _image_valeurs_trans($img, "geometrize-$nb_shapes-$opt", 'svg', $fonction, '', _SVG_SUPPORTED);
50        if (!$cache) {
51                return ("");
52        }
53
54        // facile !
55        if ($cache['format_source'] === 'svg'){
56                return $img;
57        }
58
59        if ($cache["creer"]) {
60                include_spip('filtres/couleurs');
61                include_spip('filtres/images_lib');
62                include_spip('lib/geometrize/geometrize.init');
63
64                if ($nb_shapes === 'auto' or !intval($nb_shapes)) {
65                        $auto = round(sqrt($cache['largeur'] * $cache['hauteur']) / 2);
66                        $max_shapes = (isset($options['maxShapes']) ? $options['maxShapes'] : 1200);
67                        $auto = min($auto, $max_shapes);
68                        if (strpos($nb_shapes, 'x') === 0 and is_numeric($coeff = substr($nb_shapes,1))) {
69                                $auto = round($auto * $coeff);
70                        }
71                        $nb_shapes = $auto;
72                }
73
74                $geometrize_options = [
75                        // toutes ces shapes sont viables : rapides a calculer et compact a l'export SVG
76                        // "shapeTypes" => [ShapeTypes::T_TRIANGLE,ShapeTypes::T_RECTANGLE,ShapeTypes::T_LINE],
77                        // mais c'est plus joli avec juste des triangles :)
78                        "shapeTypes" => [ShapeTypes::T_TRIANGLE],
79                        "alpha" => 255, // beaucoup plus rapide qu'avec une transparence
80                        "candidateShapesPerStep" => 100,
81                        "shapeMutationsPerStep" => 50,
82                        "steps" => $nb_shapes,
83                ];
84
85                if (isset($options['shapes']) and $shapes = _geometrize_shapes_option($options['shapes'])) {
86                        $geometrize_options['shapeTypes'] = $shapes;
87                        unset($options['shapes']);
88                }
89                if (isset($options['alpha'])) {
90                        $alpha = min(intval($options['alpha']), 96); // si plus transparent que 96 ca devient assez inutilement couteux en temps
91                        $alpha = max($options['alpha'], $alpha);
92                        $geometrize_options['alpha'] = round(255 * (127 - $alpha) / 127.0);
93                        unset($options['alpha']);
94                }
95                if (isset($options['background'])) {
96                        $couleur_bg = $options['background'];
97                        if ($couleur_bg and !in_array($couleur_bg, ['auto', 'transparent']) and !is_bool($couleur_bg)) {
98                                $couleur_bg = couleur_html_to_hex($options['background']);
99                                $couleur_bg = '#' . ltrim($couleur_bg, '#');
100                        }
101                        unset($options['background']);
102                }
103                // les autres options
104                foreach ($options as $k=>$v) {
105                        if (isset($geometrize_options[$k]) and is_scalar($geometrize_options[$k]) and is_scalar($v)) {
106                                $geometrize_options[$k] = $v;
107                        }
108                }
109
110                // temps maxi en secondes par iteration
111                // si on a pas fini on renvoie une image incomplete et on finira au calcul suivant
112                $time_budget = 20;
113
114                // le premieres iterations sont sur une petite miniature
115                // et plus on veut de details plus on augmente la taille de l'image de travail
116                // les sizes sont tricky pour avoir un x rescale = 2 = (129 - 1) / (65 - 1) car on rescale de 0 à 64px -> 0 à 128px
117                $resize_strategy = [
118                        65 => 20,
119                        129 => 100,
120                        257 => 1000,
121                        513 => 5000,
122                ];
123
124                $fichier = $cache["fichier"];
125                $dest = $cache["fichier_dest"];
126
127                if (!isset($couleur_bg)) {
128                        $couleur_bg = 'auto';
129                }
130
131                $runner = null;
132                $width_thumb = array_keys($resize_strategy);
133                $width_thumb = reset($width_thumb);
134
135                // si on avait pas fini au calcul suivant on a stocke un runner ici pour continuer le calcul
136                if (file_exists("$dest.runner")){
137                        lire_fichier("$dest.runner", $r);
138                        if ($r = unserialize($r)
139                                and $version = array_shift($r)
140                                and $version === Version::VERSION){
141                                list($runner) = $r;
142
143                                $widthModel = $runner->getModel()->getWidth();
144
145                                foreach ($resize_strategy as $wt => $n){
146                                        if ($wt<=$widthModel){
147                                                $width_thumb = $wt;
148                                        }
149                                }
150                        }
151                        unset($r);
152                }
153
154                if (!$runner){
155                        $runner = _init_geometrize_runner($img, $width_thumb, $couleur_bg);
156                }
157
158                $start_time = time();
159                spip_timer('runner');
160                for ($i = $runner->getNbSteps(); $i<$geometrize_options['steps']; $i++){
161
162                        // faut-il passer a une taille de vignette superieure ?
163                        if ($i>$resize_strategy[$width_thumb]){
164
165                                foreach ($resize_strategy as $wt => $n){
166                                        $width_thumb = $wt;
167                                        if ($n>$i){
168                                                break;
169                                        }
170                                }
171
172                                //var_dump("NEW WIDTHUMB $width_thumb");
173                                if ($width_thumb>$runner->getModel()->getWidth()){
174                                        // reinit le modele et resizer les shapes au passage
175                                        $runner = _init_geometrize_runner($img, $width_thumb, $couleur_bg, $runner);
176                                }
177                        }
178
179                        $runner->steps($geometrize_options, 1);
180
181                        if (time()>$start_time+$time_budget){
182                                break;
183                        }
184                }
185
186                $time_compute = spip_timer('runner');
187                $approx = 100.0 - round($runner->getScore() * 100,2);
188
189                // DEBUG : export au format png pour verifier l'image interne de Geometrize
190                #$png_file = substr($dest, 0, -4) . ".png";
191                #if ($runner->getModel()->getCurrent()->exportToImageFile($png_file, 'png')) {
192                #       echo("<img src='$png_file' style='width:1200px;height: auto'/>");
193                #}
194
195                // Exporter l'image SVG
196                $source_width = $cache['largeur'];
197                $source_height = $cache['hauteur'];
198                $svg_image = $runner->exportToSVG($source_width, $source_height);
199
200                // optimize the size :
201                $svg_image = str_replace(' />', '/>', $svg_image);
202                $svg_image = str_replace(">\n", ">", $svg_image);
203                $svg_image = trim($svg_image);
204
205                ecrire_fichier($dest, $svg_image);
206
207                $nsteps = $runner->getNbSteps();
208                if ($nsteps<$geometrize_options['steps']){
209                        @touch($dest, 1); // on antidate l'image pour revenir ici au prochain affichage
210                        ecrire_fichier("$dest.runner", serialize([Version::VERSION, $runner]));
211                        spip_log("PROGRESS: $fichier t=$time_compute Steps:$nsteps/".$geometrize_options['steps']." approx:$approx% length:" . strlen($svg_image), 'image_geometrize');
212                } else {
213                        @unlink("$dest.runner");
214                        spip_log("FINISHED: $fichier t=$time_compute Steps:$nsteps approx:$approx% length:" . strlen($svg_image), 'image_geometrize');
215                }
216
217        }
218
219        return $deja[$img] = image_graver(_image_ecrire_tag($cache, array('src' => $cache["fichier_dest"])));
220
221}
222
223
224/**
225 * Convertir l'option shapes au format chaine en tableau de formes selon la convention geometrize
226 * @param string $shapes_str
227 * @return array
228 */
229function _geometrize_shapes_option($shapes_str) {
230        $shapes_str = explode(',', $shapes_str);
231        $shapes_str = array_map('trim', $shapes_str);
232        $shapes = [];
233        foreach ($shapes_str as $s) {
234                $s = str_replace(' ', '_', $s);
235                switch ($s) {
236                        case 'rectangle':
237                                $shapes[] = ShapeTypes::T_RECTANGLE;
238                                break;
239                        case 'rotated_rectangle':
240                        case 'rectangle_tourne':
241                                $shapes[] = ShapeTypes::T_ROTATED_RECTANGLE;
242                                break;
243                        case 'triangle':
244                                $shapes[] = ShapeTypes::T_TRIANGLE;
245                                break;
246                        case 'ellipse':
247                                $shapes[] = ShapeTypes::T_ELLIPSE;
248                                break;
249                        case 'rotated_ellipse':
250                        case 'ellipse_tournee':
251                                $shapes[] = ShapeTypes::T_ROTATED_ELLIPSE;
252                                break;
253                        case 'circle':
254                        case 'cercle':
255                                $shapes[] = ShapeTypes::T_CIRCLE;
256                                break;
257                        case 'line':
258                        case 'ligne':
259                                $shapes[] = ShapeTypes::T_LINE;
260                                break;
261                        case 'bezier':
262                                $shapes[] = ShapeTypes::T_QUADRATIC_BEZIER;
263                                break;
264
265                        default:
266                                break;
267                }
268        }
269
270        return $shapes;
271}
272
273/**
274 * Initialize un runner avec une miniature dont les dimensions sont donnees
275 * Redimensionne les resultats exitants si besoin
276 *
277 * @param $img
278 * @param $width_thumb
279 * @param $couleur_bg
280 * @param null ImageRunner
281 * @return ImageRunner
282 * @throws HException
283 */
284function _init_geometrize_runner($img, $width_thumb, $couleur_bg, $runner = null){
285        $thumb = image_reduire($img, $width_thumb);
286        $source = extraire_attribut($thumb, 'src');
287        $bitmap = Bitmap::createFromImageFile($source);
288        if (is_null($runner)) {
289                if ($couleur_bg === true or $couleur_bg === 'auto') {
290                        // perf issue : on utilise le filtre SPIP qui opere sur une vignette de 32px et a un cache
291                        $palette = extraire_palette_couleurs($img, 3, 32);
292                        $couleur_bg = _couleur_to_geometrize(reset($palette));
293                }
294                elseif (!$couleur_bg or $couleur_bg === 'transparent') {
295                        $couleur_bg = false;
296                }
297                else {
298                        $couleur_bg = _couleur_to_geometrize($couleur_bg);
299                }
300                $runner = new ImageRunner($bitmap, $couleur_bg);
301        }
302        else {
303                $runner->reScale($bitmap);
304        }
305
306        return $runner;
307}
308
309
310/**
311 * Convertir une couleur hexa ou rgb SPIP en couleur geometrize encodee en entier sur 32bits
312 * @param string|array $c
313 * @return int
314 */
315function _couleur_to_geometrize($c){
316        if (is_string($c)){
317                $c = _couleur_hex_to_dec($c);
318        }
319        if (isset($c['alpha'])){
320                // alpha definition is the opposite (255 is opaque, 0 is transparent)
321                $c['alpha'] = round((127-$c['alpha'])*255/127);
322        } else {
323                $c['alpha'] = 255;
324        }
325        $couleur = ($c['red'] << 24)+($c['green'] << 16)+($c['blue'] << 8)+$c['alpha'];
326        return $couleur;
327}
Note: See TracBrowser for help on using the repository browser.