2015-11-02 1 views
0

J'essaye de recréer la boîte de dialogue d'image de Recolor que Microsoft a malheureusement interrompue dans la transition d'Office 2003 à 2007. C'était très utile pour remplacer des couleurs dans une image (voir http://www.indezine.com/products/powerpoint/learn/picturesandvisuals/recolor-pictures-ppt2003.html pour description complète du dialogue).Rechercher/remplacer des couleurs dans (c.-à-d. Recolorer) une image

enter image description here

Je suis surtout intéressé à faire pour des images dans le métafichier le format (EMF ou WMF), qui ont tendance à avoir moins de couleurs que d'autres formats d'image, dans mon expérience. L'image ci-dessous est un exemple d'une image de métafichier amélioré collé à partir d'Excel dans PowerPoint qui semble contenir seulement 6 couleurs:

enter image description here

Si je pouvais utiliser la boîte de dialogue de bureau existant sur la photo ci-dessus, je verrais ma 6 couleurs sur la gauche dans la colonne "Original", et je pourrais facilement changer la couleur de la police bleue (et de la bordure) en noir. Le problème ici est que si j'utilise GetPixel() pour l'inventaire par programme les couleurs de l'image, je reçois des dizaines de couleurs dues à l'anti-aliasing des polices, et il n'est pas pratique de montrer à l'utilisateur toutes ces options de recoloration (qui seraient effectivement demander à l'utilisateur de recréer manuellement l'effet anti-aliasing approprié). L'extrait de code ci-dessous illustre comment j'ai essayé d'inventorier les couleurs:

Dim listColors as New List(Of Color) 
Dim shp as PowerPoint.Shape = [a metafile picture in PowerPoint] 
Dim strTemp as String = Path.Combine(Environ("temp"), "temp_img.emf") 
shp.Export(strTemp, PowerPoint.PpShapeFormat.ppShapeFormatEMF, 0, 0) 
Using bmp As New Bitmap(strTemp) 
    For x As Integer = 0 To bmp.Width - 1 
     For y As Integer = 0 To bmp.Height - 1 
      listColors.Add(bmp.GetPixel(x, y)) 
     Next 
    Next 
End Using 

Je vois qu'il ya une Palette property en option pour métafichiers, que je pensais pouvoir apporter une réponse, mais une exception est levée lorsque je tente de y accéder, donc c'était une impasse. Je vois aussi qu'il y a headers pour les images de métafichiers, mais je ne peux pas déchiffrer quoi faire avec la documentation limitée sur Internet, et je ne suis même pas sûr que cela m'amène à la bonne réponse (c'est-à-dire 6 couleurs). En résumé, la première partie de la question est de savoir comment inventorier (c.-à-d. Identifier) ​​les six couleurs "de base" dans l'image ci-dessus et la deuxième partie comment remplacer une de ces six couleurs par une autre. Les solutions VB.NET sont préférées, bien que je puisse probablement traduire le code C# si ce n'est pas trop complexe.

Si nécessaire, vous pouvez télécharger la version EMF de l'image ci-dessus au https://www.dropbox.com/s/n03ys3dh9pcd0xu/temp_img.emf?dl=0. Pour être clair, je ne suis pas intéressé à "calculer" les six couleurs "de base" dans l'image ci-dessus. Je crois, peut-être à tort, que ces six couleurs sont des propriétés explicites de l'image, et mon premier objectif est de trouver comment y accéder. En effet, si vous dissociez simplement l'image de métafichier deux fois dans PowerPoint, vous pouvez parcourir les formes résultantes pour obtenir ces six couleurs. Cela répondrait à la partie 1 de la question, même si elle semble un peu bâclée, ne fonctionne que pour les métafichiers (ce qui peut être bien, en fait), et je doute que ce soit le cas de l'ancienne boîte de dialogue Recolor. Pour adresser la partie 2, je pourrais regrouper les formes d'image de métafichier après échange de couleurs mais, encore une fois, cela semble bâclé et modifie l'image d'autres manières que prévues. Alors, comment puis-je explicitement récupérer/modifier/définir ces couleurs "de base" dans une image [métafichier]?

+1

Je pense que le problème est les polices anti-aliasées, vous devrez soit modifier l'image avant d'obtenir les pixels, les obtenir tous ou sélectionner toutes les couleurs similaires – TheLethalCoder

+0

Si vous utilisez un verre à l'écran et regardez de près les polices d'écran, vous voyez un halo multicolore autour d'eux, même s'ils sont noirs. – TaW

+0

@TaW Je crois que c'est ce que le problème OP est, ou au moins une partie de celui-ci – TheLethalCoder

Répondre

0

J'ai donc essayé de mettre en œuvre l'une de mes idées pour utiliser un dictionnaire de couleurs. Le code ne fonctionne pas correctement pour le moment, mais je me suis dit que je le montrais ici pour que vous puissiez jeter un coup d'œil rapide sur la façon dont cela fonctionne et développer à partir de là.

using (Bitmap bitmap = new Bitmap(@"InputPath")) 
{ 
    Dictionary<Color, List<Color>> colourDictionary = new Dictionary<Color, List<Color>>(); 
    int nTolerance = 60; 

    int nBytesPerPixel = Bitmap.GetPixelFormatSize(bitmap.PixelFormat)/8; 
    System.Drawing.Imaging.BitmapData bitmapData = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), System.Drawing.Imaging.ImageLockMode.ReadOnly, bitmap.PixelFormat); 
    try 
    { 
     int nByteCount = bitmapData.Stride * bitmap.Height; 
     byte[] _baPixels = new byte[nByteCount]; 

     System.Runtime.InteropServices.Marshal.Copy(bitmapData.Scan0, _baPixels, 0, _baPixels.Length); 
     int _nStride = bitmapData.Stride; 

     for (int h = 0; h < bitmap.Height; h++) 
     { 
      int nCurrentLine = h * _nStride; 
      for (int w = 0; w < (bitmap.Width * nBytesPerPixel); w += nBytesPerPixel) 
      { 
       int nBlue = _baPixels[nCurrentLine + w]; 
       int nGreen = _baPixels[nCurrentLine + w + 1]; 
       int nRed = _baPixels[nCurrentLine + w + 2]; 

       if (colourDictionary.Keys.Count > 0) 
       { 
        Color[] caNearbyColours = colourDictionary.Keys.Select(c => c) 
         .Where(c => (int)c.B <= (nBlue + nTolerance) && (int)c.B >= (nBlue - nTolerance) 
          && (int)c.G <= (nGreen + nTolerance) && (int)c.G >= (nGreen - nTolerance) 
          && (int)c.R <= (nRed + nTolerance) && (int)c.R >= (nRed - nTolerance)).ToArray(); 

        if (caNearbyColours.Length > 0) 
        { 
         if (!colourDictionary[caNearbyColours.FirstOrDefault()].Any(c => c.R == nRed && c.G == nGreen && c.B == nBlue)) 
          colourDictionary[caNearbyColours.FirstOrDefault()].Add(Color.FromArgb(255, nRed, nGreen, nBlue)); 
        } 
        else 
         colourDictionary.Add(Color.FromArgb(255, nRed, nGreen, nBlue), new List<Color>()); 
       } 
       else 
        colourDictionary.Add(Color.FromArgb(255, nRed, nGreen, nBlue), new List<Color>()); 
      } 
     } 
    } 
    finally 
    { 
     bitmap.UnlockBits(bitmapData); 
    } 

    using (Bitmap colourBitmap = new Bitmap(bitmap.Width, bitmap.Height, bitmap.PixelFormat)) 
    { 
     using (Graphics g = Graphics.FromImage(colourBitmap)) 
     { 
      for (int h = 0; h < colourBitmap.Height; h++) 
      { 
       for (int w = 0; w < colourBitmap.Width; w++) 
       { 
        Color colour = bitmap.GetPixel(w, h); 
        if (!colourDictionary.ContainsKey(colour)) 
        { 
         Color keyColour = colourDictionary.Keys.FirstOrDefault(k => colourDictionary[k].Any(v => v == colour)); 

         colourBitmap.SetPixel(w, h, keyColour); 
        } 
        else 
         colourBitmap.SetPixel(w, h, colour); 
       } 
      } 
     } 

     colourBitmap.Save(@"OutputPath", System.Drawing.Imaging.ImageFormat.Png); 
    } 
} 

Remarquez comment la partie supérieure utilise Lockbits pour une meilleure performance. Cela peut facilement être transféré sur la section inférieure.Notez également que le code de bits de verrouillage est réglé pour travailler pour les images avec un nombre d'octets par pixel de 3 ou 4.

maintenant sur la façon dont le code tente de travailler:

Il commence par une boucle sur l'image initiale et trouver des couleurs. Si la couleur est déjà trouvée elle saute, cependant si la couleur n'est pas dans le dictionnaire elle l'ajoutera et aussi si la couleur a une couleur semblable dans les clés de dictionnaires (il faudra changer pour regarder les valeurs aussi) il l'ajoutera à ses valeurs.

Ensuite, il fait défiler les pixels de l'image de sortie en les ajustant en fonction des touches du dictionnaire, créant ainsi une image «bloquée».

Maintenant, comme je l'ai dit qu'il ne fonctionne pas tout de suite, alors voici quelques améliorations devaient être apportées:

  • Comme dit plus haut contrôle de la couleur à ceux des valeurs
  • Ajout d'éléments de verrouillage à le code en bas pour une meilleure performance
  • Peaufiner la valeur nTolerance pour de meilleurs résultats
  • de garder trace de comptes de couleurs et à la fin de looping définir la clé pour que le plus grand nombre
  • Et bien sûr toute autre chose que je n'ai pas pensé
+0

@MacG J'espère que cela vous aidera à démarrer dans votre problème – TheLethalCoder

+0

Merci. Je suis conscient de LockBits comme une alternative plus rapide à GetPixel() pour obtenir des couleurs. Cependant, LockBits retournera toujours toutes les couleurs de l'image, y compris celles utilisées pour l'anti-aliasing, non? Pour contourner cela, je comprends que vous utilisez une tolérance aux couleurs "seau" en moins de seaux, mais cela semble peu fiable pour obtenir toujours les bonnes couleurs "de base" (6, dans cet exemple). De plus, même si le concept de tolérance permettait d'inventorier correctement les 6 couleurs de la partie 1, cela créerait d'autres problèmes lors du remplacement des couleurs de la partie 2, comme par exemple un anti-aliasing de faible qualité ou pas. – OfficeAddinDev

+0

@MacG Bien sûr, avec cette approche, vous allez perdre la qualité et l'effet anti-aliasing. C'était une idée que je pensais pouvoir vous aider à démarrer, vous pensez. Mais la manière d'essayer de minimiser les faux positifs du seau de tolérance est de faire de la couleur principale celle qui apparaît le plus. Vous pouvez peut-être conserver l'effet anti-aliasing au lieu d'utiliser SetPixel en utilisant Graphics et en définissant la qualité de lissage. – TheLethalCoder