Catégorie : développement – divers

Php : challenge ASCII art

J’ai fait un pari. Petit challenge à réaliser : faire un script (peu importe le langage) qui prend en paramètre un fichier image, et qui en sort de l’ASCII art.
Il faut qu’il soit rapidement paramétrable sur la taille des fontes.

Je l’ai fait « théorique » en 59 minutes, mais entièrement fonctionnel en une heure trente. J’ai fait deux scripts : un qui génère le fichier des fontes, et le principal qui lit le fichier image des fontes, et le fichier image en paramètre, et sort de l’ASCII art.

Exemple de résultat (si si c’est bien du texte, vous pouvez le sélectionner et copier coller en tant que texte !) :

BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBMBBBBBBBBBBBB
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBB¨ MBBBBBBMM0BBB
BBBBBBBBBBBBBBBBBBBBBBBBBP'`BB  ,BBBP¨    MBB
BBBBBBBBBBBBBBBBBBBBBBBBB&  %BMBPBBL  ,a  ¨BB
BBBBBBBBBBBBBBBBBBBBBBBBBB  jBB  MBBaBBP   BB
BBBBBBBBBBBBBBBBBBBBBBBBPM   BB  ¨BBB^  ,  0B
BBBBBBBBBBBBBBBBBBBBBBP`     BBb  BB'  mB  jB
BBBBBBB@P'BBBBBB@M@BBB   mB  $BK  0B  jBP  ¨B
BBBBBBBK  BBBP^`   MBB  dBB  MBB  jB   ¨ L,aB
B^¨ 'BBB  %BB  ,a   BB  &BBL  BB   Bh  ,WBBBB
B    ¨BB  jBB,mBM^  $B  'BBF  BBL  BBBBBBBBBB
BL    `BL  BBBP¨    jB   ^^   %B&mBBBBBBBBBBB
B&  w  ¨&  BBP  wB   B&    wamBBBBBBBBBBBBBBB
BB  %w     MBL  BB¨  BBBwamBBBBBBBBBBBBBBBBBB
BB  MBw    ¨BK  ¨`, ,&BBBBBBBBBBBBBBBBBBBBBBB
BBL  BBN    BB,  aBBBBBBBBBBBBBBBBBBBBBBBBBBB
BB&  BBBh   0BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
BBB  MBBBhwmBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
BBB  OBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB

Ne regardez pas la suite si vous voulez essayer de le faire pour vous !

<?php
function usage_et_exit($message = '', $exit_error = -1) {
    global $argv;
    if ($message != '') {
        echo $message."\n";
    }
    echo $argv[0]." {image gif reference} {image noir et blanc jpg}\n";
    exit($exit_error);
}

function noir_ou_blanc($r, $g, $b) {
    /* http://stackoverflow.com/questions/254388/how-do-you-convert-an-image-to-black-and-white-in-php */
    if (((0.299*$r) + (0.587*$g) + (0.114*$b))> 0x7F) {
        return 0xFFFFFF;
    } else {
        return 0x000000;
    }
}

function cherche_meilleure_correspondance(
    $img_src, $x, $y, $img_fonts, $font_size, $tab_base
) {
    $meilleur = -1;
    $retour = '';
    foreach ($tab_base as $idx => $ch) {
        $match = 0;
        for ($t_y=0; $t_y<$font_size; $t_y++) {
            for ($t_x=0; $t_x<$font_size; $t_x++) {
                if (imagecolorat($img_src, $t_x+$x, $t_y+$y)
                    != imagecolorat(
                        $img_fonts,
                        ($idx*$font_size) + $t_x, $t_y
                    )
                ) {
                    $match++;
                }
            }
        }
        if ($match>$meilleur) {
            $retour = $ch;
            $meilleur = $match;
        }
    }
    return $retour;
}

function convert_img_noir_et_blanc($filename, $img)
{
    list($width, $height, $type, $attr) = getimagesize($filename);
    /* Conversion en noir et blanc */
    for ($y=0; $y<$height; $y++) {
        for ($x=0; $x<$width; $x++) {
            $rgb = imagecolorat($img, $x, $y);
            $r = ($rgb >> 16) & 0xFF;
            $g = ($rgb >> 8) & 0xFF;
            $b = $rgb & 0xFF;
            $nb = noir_ou_blanc($r, $g, $b);
            imagesetpixel($img, $x, $y, $nb);
        }
    }
}

$tab_base = array(
    '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N',
    'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c',
    'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q',
    'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4',
    '5', '6', '7', '8', '9', '&', '\'', ',', ';', ':', '?', '.', '/', '!',
    '%', '^', '¨', '$', '~', '#', '(', ')', '[', ']', '|', '-', '`', ' '
);
$font_size = 14;
$min_width  = 400;
$min_height = 400;

if ($argc!=3) {
    usage_et_exit("Arguments invalides", -1);
}

$img_tmp = imagecreatefromstring(file_get_contents($argv[1]));
$width = imagesx($img_tmp);
$height = imagesy($img_tmp);
$img_fonts = imagecreatetruecolor($width, $height);
imagecopy($img_fonts, $img_tmp, 0, 0, 0, 0, $width, $height);
imagedestroy($img_tmp);
unset($img_tmp);
if ($img_fonts===false) {
    usage_et_exit("Erreur chargement de ".$argv[1], -1);
}

/* Conversion en noir et blanc des fontes */
convert_img_noir_et_blanc($argv[1], $img_fonts);
imagepng($img_fonts, './resultat_fonts.png', 0);

list($width, $height, $type, $attr) = getimagesize($argv[2]);
if ($width<$min_width) {
    usage_et_exit("Largeur trop petite de ".$argv[1].", taille min = ".$width_min, -1);
}
if ($width<$min_height) {
    usage_et_exit("Hauteur trop petite de ".$argv[1].", taille min = ".$width_min, -1);
}

$img_src = imagecreatefromjpeg($argv[2]);
if ($img_src===false) {
    usage_et_exit("Erreur chargement de ".$argv[2], -1);
}
convert_img_noir_et_blanc($argv[2], $img_src);
/* Ok on a l'image en noir et blanc. Parcourir toute l'image */
imagepng($img_src, './resultat_noir_et_blanc.png', 0);

$y = 0;
$max_result = 0;
while (($y + $font_size) < $height) {
    $x = 0;
    while (($x + $font_size) < $width) {
        echo cherche_meilleure_correspondance(
            $img_src, $x, $y, $img_fonts, $font_size, $tab_base
        );
        $x += $font_size;
    }
    echo "\n";
    $y += $font_size;
}
echo "Done.\n";
?>

Et le fichier qui génère les fontes (22 minutes de perdues à le faire…) :

<?php
$tab_base = array(
    '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N',
    'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c',
    'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q',
    'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4',
    '5', '6', '7', '8', '9', '&', '\'', ',', ';', ':', '?', '.', '/', '!',
    '%', '^', '¨', '$', '~', '#', '(', ')', '[', ']', '|', '-', '`', ' '
);
$x=0;
$font_size = 14;
$width = count($tab_base)*$font_size;
echo 'width='.var_export($width,true)."\n";
$height = $font_size*4;
$img = imagecreatetruecolor($width, $height);
if ($img===false) {
    throw new Exception("Erreur");
}
imagefilledrectangle($img, 0, 0, $width, $height, 0xFFFFFF);
foreach ($tab_base as $idx => $char) {
    $ok = imagettftext(
        $img,
        $font_size, 0,
        $idx * $font_size, $font_size,
        0x000000,
        './consola.ttf', $char
    );
}
imagegif($img, './test.gif');
echo "Done.\n";
?>

Symfony 2 : [Semantical Error] The annotation « @ManyToMany » in property … was never imported.

Si jamais un jour, vous tentez de déclarer à la main une relation de type OneToOne, OneToMany ou ManyToMany et que vous avez une erreur de ce genre :

[Semantical Error] The annotation "@ManyToMany" in property MaSociete\PersoBundle\Entity\MaClasse::$proprietes was never imported. Did you maybe forget to add a "use" statement for this annotation?

Alors vous vous êtes sûrement aidé, comme moi, de la documentation officielle qui donne ces exemples, que je copie colle ici :

<?php
/** @Entity **/
class User
{
    // ...

    /**
     * @ManyToMany(targetEntity="Group", inversedBy="users")
     * @JoinTable(name="users_groups")
     **/
    private $groups;

    public function __construct() {
        $this->groups = new \Doctrine\Common\Collections\ArrayCollection();
    }

    // ...
}

/** @Entity **/
class Group
{
    // ...
    /**
     * @ManyToMany(targetEntity="User", mappedBy="groups")
     **/
    private $users;

    public function __construct() {
        $this->users = new \Doctrine\Common\Collections\ArrayCollection();
    }

    // ...
}

Si cela ne fonctionne pas et que vous avez cette erreur :

[Semantical Error] The annotation "@ManyToMany" in property MaSociete\PersoBundle\Entity\MaClasse::$proprietes was never imported. Did you maybe forget to add a "use" statement for this annotation?

Alors c’est qu’il suffit simplement d’ajouter le mot ORM\.

Ainsi mon code qui ne fonctionnait pas :

<?php
/**
 * @ManyToMany(targetEntity="Partenaire", inversedBy="personnes")
 * @JoinTable(name="personne_partenaire")
 **/
?>

Et le code qui fonctionne :

<?php
/**
 * @ORM\ManyToMany(targetEntity="Partenaire", inversedBy="personnes")
 * @ORM\JoinTable(name="personne_partenaire")
 **/
?>

Symfony 2 et vim : coloration syntaxique twig / twig syntax highlighting

Comment faire pour mettre en place la coloration syntaxique sous vim ?

Rien de plus simple : il faut aller ici.

Et ensuite vous allez dans votre répertoire de configuration vim, qui est le plus souvent : .vimrc.

De là, vous faites un copier coller « à la main » des fichiers de configuration dans les répertoires adéquats.

N’hésitez pas pour une chose : si les répertoires de destination n’existent pas, créez-les !

Bien sûr, il y a des façons plus propres de procéder, notamment en utilisant vimplugin, mais je l’ai fait « vite », et cela a très bien fonctionné cinq fois, sur cinq PC et distributions différentes, y compris sous Cygwin.

Drupal : code pour les liens avec des URLs dynamiques

Drupal : comment s’adapter à une migration si le site n’est plus à la racine ?

Dans les modules, activation du module « Php filter », afin que le code Php soit exécuté.
Et dans le texte qu’il y a dans un bloc « menu », par exemple, vous pourriez vous inspirer d’un code comme celui-ci (sachant qu’il vous faut absolument revoir l’indentation, qui n’est pas acceptable pour un code digne de ce nom) 🙂  :

<?php /* Ce code sert à générer les URLs
 * de manière dynamique, afin que si l'installation
 * Drupal est dans un sous-répertoire, les liens
 * soient "automatiquement" adaptés, via
 * le code "'absolute' => TRUE", 
 */ ?>
<ol>
    <li>
        <?php echo l(t('Menu1'),
            'lien1',
            array( 'absolute' => TRUE, 'query' => array()
        )); ?>
    </li>
    <li>
        <?php echo l(t('Menu2'),
            'lien2',
            array( 'absolute' => TRUE, 'query' => array()
        )); ?>
    </li>
    <li>
        <?php echo l(t('Menu3'),
            'lien3',
            array( 'absolute' => TRUE, 'query' => array()
        )); ?>
    </li>
    <li>
        <?php echo l(t('Menu4'),
            'lien4',
            array( 'absolute' => TRUE, 'query' => array()
        )); ?>
    </li>
</ol>

DNS et noms de domaine : one and one, 1and1 … sous DNS 10 max !

Petite information (valable au jour même de ce billet) :

Si vous achetez des noms de domaine :

  • chez ovh.fr vous ferez tout ce que vous voulez avec vos noms de domaines : c’est long mais c’est le meilleur rapport qualité prix ;
  • chez gandi.net vous ferez tout ce que vous voulez avec vos noms de domaines et vous pourrez même faire des « modèles » de vos configurations DNS pour les appliquer partout (pratique quand on a plein de noms de domaines) ;
  • chez oneandone.fr (ou 1and1.fr) vous serez limité à dix sous domaines pour la totalité de votre compte !

Exemple concret : j’ai un partenaire, pizzamis.fr qui a acheté son nom de domaine chez oneandone.fr (ou 1and1.fr).

Les sites que je fait sont très optimisés et se servent, notamment, de la fonctionnalité de séparation des sous domaines pour télécharger en parallèle (voir les conseils de YSlow).

Donc pour un site, j’ai les préfixes « s » comme « static » puis un chiffre de 1 à 4. Et les préfixes de la langue (« fr », « us » etc).

Donc pour ce partenaire, j’ai ces préfixes :

  • fr.pizzamis.fr
  • fr.s1.pizzamis.fr
  • fr.s2.pizzamis.fr
  • fr.s3.pizzamis.fr
  • fr.s4.pizzamis.fr

Cinq préfixes. Pour la version téléphone mobile, même chose : cinq préfixes.
Sur gandi, même chose que sur ovh : pas besoin de configurer les préfixes : on configure en mettant « * CNAME . » et tout fonctionne.

Ici, voici le résultat :

Limite 10 sous domaines chez 1and1

Oui oui vous avez bien lu :

10 utilisés sur 10 disponibles.

Après, à vous de juger, je dis ça, mais c’est un constat, tout est objectif !
Je n’ai pas dit de ne jamais aller chez oneandone.fr (ou 1and1.fr) 😉 !

Vim: comment remplacer tous les mots par celui sous le curseur?

Je n’ai pas été bien loin, vous trouverez la version anglaise ici.

:%s/mot/<c-r><c-a>/g

Remplacer chaque occurrence de ‘mot’ avec le mot sous le curseur (le mot est délimité par des espaces ou des retours à la ligne).

<c-r><c-a> signifie qu’il faut appuyer sur Ctrl et R puis sur Ctrl et A.

Le mot sous le curseur sera automatiquement inséré dans la ligne de commande exactement comme il apparaît à l’écran.

Le côté génial, c’est que vous pouvez enregistrer cela dans une macro et il se souviendra du principe !

Exemple concret : j’ai une centaine de partenaires auquels je dois appliquer la même opération SQL. Voilà comment je me suis servi de la fonctionnalité :

  • J’ai mis toute la liste des partenaires dans un fichier, un partenaire par ligne ;
  • J’ai crée une seule fois, pour le partenaire 1, la série de commandes SQL ;
  • Ensuite, j’ai copié collé une centaine de fois cette même série pour tous les partenaires (sous vim: « chiffre+@a » et il joue chiffre fois la macro « a », donc ça ne m’a pris que le temps de taper une dizaine de touches) ;
  • J’ai enregistré la macro suivante, que je vous explique en français : «sélectionne la série de commande courante, monte d’une ligne pour aller sur le nom du partenaire courant, et remplace toutes les occurences de «partenaire1» par le mot sous le curseur.

Ensuite, une fois la macro enregistrée, j’ai tapé « 100@a » = jouer 100 fois la macro « a ».

Et voilà.

Beaucoup de temps de gagné !

PhpDocumentor 2 : howto et résultats exceptionnels

Après plusieurs échanges avec le développeur principal de PhpDocumentor 2, celui-ci a corrigé plusieurs bogues et maintenant la version alpha tourne, et j’ai pu la lancer sur mon framework.

Résultats tout simplement exceptionnels (pas pour mon framework, mais pour PhpDocumentor).

Ce qui m’a le plus bluffé c’est le schéma de diagramme de classes : non seulement il est beau et pratique, mais il est clair.

Donc une vue globale de mon diagramme de classes :

Image petite du diagramme de classes

Et quand on clique pour zoomer, le rendu est tout aussi exceptionnel :

Image petite du zoom du diagramme de classes

C’est pratique, grâce à ça, je vois tout ce qui manque. Par exemple, les ListeXXX ne sont pas dans une classe générique, alors que les ItemXXX oui, des petites évolutions à faire pour rendre un peu tout ça plus propre, mais c’est un bon début !

Ensuite, viennent les classes et la documentation générée : de la même façon, si tout est correctement organisé, le rendu est vraiment très bon et la documentation est enfin utilisable !

Image petite de l'exemple de documentation de classes

Gimp et Python-fu script : vecteurs et strokes : exemple

J’ai eu énormément de mal à trouver un exemple de script python-fu qui fonctionne.

Après avoir réussi à automatiser certaines tâches grâce à Gimp, je voulais créer des vecteurs, et y appliquer la brosse en cours.

Voici l’exemple de code qui fonctionne :


  new_image = pdb.gimp_image_new( new_image_width, new_image_height, RGB )
  new_layer = pdb.gimp_layer_new(
    new_image, new_image.width, new_image.height,
    RGBA_IMAGE, _("Background"), 100, NORMAL_MODE)
  pdb.gimp_image_add_layer(new_image, new_layer, -1)
  pdb.gimp_drawable_fill(new_layer, fill_type )
  new_layer = gimp.Layer(
    new_image, _("New Layer"),
    new_image.width, new_image.height,
    RGBA_IMAGE, 100, NORMAL_MODE)
  pdb.gimp_image_add_layer(new_image, new_layer, -1)
  pdb.gimp_drawable_fill(new_layer, fill_type )

  new_vectors=pdb.gimp_vectors_new(new_image, 'Vectors!')
  t = int(round(step / 1.5))
  pdb.gimp_vectors_stroke_new_from_points(
    new_vectors,
    0, # 0 = Beziers Curve
    30,
    # {controle1} {centre} {controle2}
  [ x-(step-t), y, x-step, y, x-(step-t), y,
      x, y+(step-t), x, y+step, x, y+(step-t),
      x+(step-t), y, x+step, y, x+(step-t), y,
      x, y-(step-t), x, y-step, x, y-(step-t),
      x-(step-t), y, x-step, y, x-(step-t), y],
      False) # Closed = True

  pdb.gimp_image_add_vectors(new_image, new_vectors, 0)

  pdb.gimp_context_set_foreground( (255,255,255) )
  pdb.gimp_context_set_brush( "Circle (07)" )
  pdb.gimp_edit_stroke_vectors(new_layer, new_vectors)

  gimp.Display( new_image )

Maintenant avec cet exemple, si vous voulez faire des vecteurs et les dessiner avec la brosse en cours, ainsi qu’avec mon article sur l’automatisation de Gimp, vous devriez arriver à faire beaucoup de choses !