Text auf Bild zeichen: Wie kontrastreiche Schriftfarbe wählen?

  • VB.NET

Es gibt 14 Antworten in diesem Thema. Der letzte Beitrag () ist von markus.obi.

    Text auf Bild zeichen: Wie kontrastreiche Schriftfarbe wählen?

    Hey,

    Ich bin grad dabei ein paar Urlaubsbilder automatisch zu beschriften.
    Auf jedes Bild wird der Bildkommentar und die Aufnahmezeit in die rechte untere Ecke geschrieben.
    Bisher benutze ich eine weiße Schrift, die sich optisch ganz gut macht und auf vielen Bildern gut zu erkennen ist.
    Natürlich gibts auch Bilder auf denen die Schrift kaum oder gar nicht zu erkennen ist. Das ist das Problem.

    Ich hätte da so an RGB Mittelwertbildung der Pixel in dem entsprechenden Rechteck gedacht.
    Dann entweder invertieren oder ab nem gewissen Helligkeitswert schwarze Schrift nehmen.
    Gibts da ne elegantere Lösung?
    Alternative zu aufwändigen Berechnungen: die Schrift zweimal zeichnen: einmal dunkel, und dann versetzt hell. Bsp:

    VB.NET-Quellcode

    1. Dim dateiname As String = "xyZ1434.jpg"
    2. Dim fnt As Font = New Font("Segoe UI", 32, FontStyle.Bold)
    3. e.Graphics.DrawString(dateiname, fnt, Brushes.Black, New PointF(11, 11))
    4. e.Graphics.DrawString(dateiname, fnt, Brushes.Black, New PointF(10, 11))
    5. e.Graphics.DrawString(dateiname, fnt, Brushes.Black, New PointF(9, 9))
    6. e.Graphics.DrawString(dateiname, fnt, Brushes.White, New PointF(10, 10))
    Dadurch kannst du der Schrift einen Rand geben. (Im Beispiel habe ich nur etwas rumgespielt. Vl. kriegst du eine bessere Lösung hin.)

    Edit:
    oder überhaupt nur Text mit Schatten zeichnen:

    VB.NET-Quellcode

    1. e.Graphics.DrawString(dateiname, fnt, Brushes.Black, New PointF(13, 13))
    2. e.Graphics.DrawString(dateiname, fnt, Brushes.White, New PointF(10, 10))

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „der_Kurt“ ()

    @der_Kurt
    Die Idee ist gut, allerdings nicht so sauber. Bei manchen Schriftarten kann es vorkommen, dass z.B. die Füllung über den Rand hinausgeht etc. Zumindest ist das meine Erfahrung damit.
    Ein GraphicsPath wäre da viel besser geeignet. Also einfach den Schriftzug auf einen GraphicsPath zeichnen, diesen dann mit einem Brush auf das Bild zeichnen und mit einem Pen umranden - das haut auf jeden Fall immer hin und liefert bestmögliche Resultate.
    In meinem GDI-Tutorial habe ich sowas gemacht, Abschnitt 7: [VB 2008] [Tutorial] GDI+

    Edit: Bin grade am überlegen, ob man mit einer ColorMatrix etwas basteln könnte. Du könntest die Ausmaße des Textes mit einem GraphicsPath darstellen, dann den "bearbeitbaren" bereich des Bildes auf den Text reduzieren und ein invertiertes Bild über das Eingangsbild zeichnen - das scheint dann natürlich nur dort durch, wo später Text ist. Das Resultat wäre, dass jeder Pixel der auf dem Schriftzug liegt invertiert wird. Auch andere Effekte (aufhellen, abdunkeln, färben) müssten so möglich sein, werde mir das mal anschauen^^

    lg

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „FreakJNS“ ()

    Wie wäre es, wenn du einfach das Bild XORst?

    VB.NET-Quellcode

    1. Dim text As String = "Testbild 20.01.13"
    2. Dim textSize As Size = TextRenderer.MeasureText(text, Me.Font)
    3. Dim BMPText As New Bitmap(textSize.Width, textSize.Height)
    4. Using g As Graphics = Graphics.FromImage(BMPText)
    5. g.DrawString(text, Me.Font, Brushes.Black, 0, 0)
    6. End Using
    7. Dim pos As New Point(currentImage.Width - BMPText.Width - 5, currentImage.Height - BMPText.Height - 5) 'currentImage ist dein zu beschriftendes Photo/Bild
    8. For x As Integer = pos.X To pos.X + BMPText.Width - 1
    9. For y As Integer = pos.Y To pos.Y + BMPText.Height - 1
    10. Dim cText As Color = BMPText.GetPixel(x - pos.X, y - pos.Y)
    11. If cText.A > 0 Then
    12. Dim cBild As Color = currentImage.GetPixel(x, y)
    13. currentImage.SetPixel(x, y, Color.FromArgb(CByte(cBild.R Xor 255), CByte(cBild.G Xor 255), CByte(cBild.B Xor 255)))
    14. End If
    15. Next
    16. Next


    Kann man natürlich auch noch mit der LockBits-Variante schneller machen, aber da die Beschriftung so klein ist, erscheint mir Set/GetPixel ausreichend schnell.

    Sieht dann so aus:
    Bilder
    • unbenannt.jpg

      76,6 kB, 538×213, 212 mal angesehen

    Skybird schrieb:

    Das sind ja Ubisoftmethoden hier !

    Das mit der Xor Variante habe ich auch schon gelesen, nur bin ich noch nicht auf die Idee gekommen jeden einzelnen Pixeln zu XORen.
    Ich muss mal schaun wie das wirkt.
    Gibt vielleicht Probleme bei mittleren Grau Werten. Der Konstrast ist leider abhängig von der Farbe.
    0 gibt 255 aber 127 gibt 128.

    Hab aber noch ein anderes Problem.
    Momentan lese ich die metadaten über die Klassen BitmapSource und BitmapMetadata ein. Beim abspeichern über Image.save(... , ...) werden aber leider die Metadaten nicht gespeichert.
    Gibts da ne Möglichkeit die zu behalten?

    VB.NET-Quellcode

    1. Imports System.Drawing.Imaging
    2. Imports System.Drawing.Drawing2D
    3. Imports System.Windows.Media.Imaging
    4. Imports System.IO
    5. Private Function addlabel(_pfad As String) As Image
    6. Dim data As New MemoryStream(File.ReadAllBytes(_pfad))
    7. Dim bitmapsrc As BitmapSource = BitmapFrame.Create(data)
    8. Dim metadata As BitmapMetadata = CType(bitmapsrc.Metadata, BitmapMetadata)
    9. Dim date1 = DateTime.Parse(metadata.DateTaken)
    10. Dim drawimage As New Bitmap(data)
    11. Dim text1 As String = metadata.Comment & ", " & date1.ToString("dd.MMMMMMMMMMMMMMMMMMMM yyyy")
    12. Dim Iwidth = drawimage.Width
    13. Dim Iheight = drawimage.Height
    14. Dim graphicImage As Graphics = Graphics.FromImage(drawimage)
    15. graphicImage.SmoothingMode = SmoothingMode.AntiAlias
    16. 'Textgröße 3% der Bildhöhe
    17. Dim fsize As Int32 = CInt(Iheight * 0.03)
    18. Dim font1 As New Font("Arial", fsize, FontStyle.Regular, GraphicsUnit.Pixel)
    19. Dim size1 = graphicImage.MeasureString(text1, font1).ToSize
    20. Dim location0 As New Point(Iwidth - size1.Width, Iheight - size1.Height)
    21. Dim locations As New List(Of Point)
    22. Dim outlinesize As Int32 = 2
    23. locations.Add(location0 - New Size(outlinesize, outlinesize))
    24. locations.Add(location0 - New Size(outlinesize, -outlinesize))
    25. locations.Add(location0 - New Size(-outlinesize, outlinesize))
    26. locations.Add(location0 - New Size(-outlinesize, -outlinesize))
    27. For Each Loc As Point In locations
    28. graphicImage.DrawString(text1, font1, Brushes.Black, Loc)
    29. Next
    30. graphicImage.DrawString(text1, font1, Brushes.White, location0)
    31. graphicImage.Dispose()
    32. data.Close()
    33. Return drawimage
    34. End Function


    Abspeichern via:

    VB.NET-Quellcode

    1. image1.Save(newname.FullName, ImageFormat.Jpeg)


    Dabei aber die metadaten verloren.

    ThuCommix schrieb:

    Man könnte auch die Durschnittsfarbe errechnen, und diese Umkehren.
    Was besonders gut funktioniert bei RGB(128, 128, 128) :S
    Auf R, G, B einzeln einen Offset addieren, bei Überlauf 255 abziehen oder so.
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    Lesbar genug?


    Geht so:

    VB.NET-Quellcode

    1. Private Sub OutlineVariant()
    2. Dim currentImage As Bitmap = CType(PictureBox1.Image, Bitmap) 'dein Photo
    3. Dim text As String = "Testbild 20.01.13"
    4. Dim textSize As Size = TextRenderer.MeasureText(text, Me.Font)
    5. Dim GP As New GraphicsPath
    6. GP.AddString(text, Me.Font.FontFamily, Me.Font.Style, Me.Font.Size, New Point(currentImage.Width - textSize.Width, currentImage.Height - textSize.Height), New StringFormat)
    7. Using g As Graphics = Graphics.FromImage(currentImage)
    8. g.SmoothingMode = SmoothingMode.HighQuality
    9. g.PixelOffsetMode = PixelOffsetMode.HighQuality
    10. Dim GPOutline As GraphicsPath = CType(GP.Clone, GraphicsPath)
    11. GPOutline.Widen(New Pen(Brushes.Black, 4))
    12. g.FillPath(Brushes.Black, GPOutline)
    13. g.FillPath(Brushes.White, GP)
    14. GPOutline.Dispose()
    15. End Using
    16. GP.Dispose()
    17. PictureBox1.Image = currentImage
    18. End Sub

    Skybird schrieb:

    Das sind ja Ubisoftmethoden hier !

    Dieser Beitrag wurde bereits 3 mal editiert, zuletzt von „vb-checker“ ()

    vb-checker schrieb:

    Lesbar genug?
    In diesem einen und vielleicht 173 weiteren Spezialfällen ja, allgemein jedoch und für einen Automaten: Nein.
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    Kann ich nicht ganz nachvollziehen. Das Anwendungsszenario ist die Beschriftung von Urlaubsbildern. Zeig mir doch mal ein Photo, bei dem diese Beschriftung nicht lesbar ist.

    Dabei aber die metadaten verloren.

    "Use the Image.PropertyItems property on the source image to get the list of metadata items. Loop through the list, calling Image.SetPropertyItem on the destination image."
    stackoverflow.com/questions/35…while-preserving-metadata

    Skybird schrieb:

    Das sind ja Ubisoftmethoden hier !

    Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von „vb-checker“ ()

    Es soll ja keine Machine lesen, sondern ein Mensch.
    Meiner Meinung nach ist es für die Augen besser eine Schrift zu lesen, die nicht innerhalb eines Buchstaben die Farbe ändert. (hab ich grad festgestellt)
    Womit dann die Pixel-Invertier-Methoden rausfallen. Weiße Schrift ließt sich auf den meisten Bildern gut und ist schön unaufällig.
    Das, zusammen mit dem schwarzen Rand, gibt ein akzeptables Ergebnis.

    Die Lösung von vb-checker funzt soweit. Sie liefert fast das gleiche Ergebnis, wie meine Methode, ist aber eleganter.
    Nur

    VB.NET-Quellcode

    1. Dim textSize As Size = TextRenderer.MeasureText(text1, font1)

    ergibt eine etwas zu geringe Breite.

    VB.NET-Quellcode

    1. Graphics1.MeasureString(text1, font1).ToSize
    passt dagegen perfekt.

    Das mit den Metadaten muss ich gleich mal ausprobieren, sollte funktionieren.
    Der Link von StackOverFlow trifft auf mich nicht zu.
    Ich habe kein Source oder Destination Bild.
    Ich habe nur ein Bild (bitmap), das ich mir aus dem MemoryStream hole.
    Dieses speichere ich mit .save ab. Die Propertyitems sollten eigentlich nicht verloren gehen.

    Ich habe versucht das Problem zu finden und eine extrem seltsame Verhaltensweise entdeckt.
    Wenn ich in Zeile 30

    VB.NET-Quellcode

    1. Debug.WriteLine(bitmap1.PropertyItems.Length)

    auskommentiere, dann sind die Propertyitems plötzlich weg. Wenn ich beide Debug.WriteLine Zeile drin habe, dann sind sie da!
    Irgendwo ist da der Wurm drin!

    Spoiler anzeigen

    VB.NET-Quellcode

    1. Private Function addlabel(_pfad As String) As Bitmap
    2. Dim data As New MemoryStream(File.ReadAllBytes(_pfad))
    3. Dim bitmapsrc As BitmapSource = BitmapFrame.Create(data)
    4. Dim metadata As BitmapMetadata = CType(bitmapsrc.Metadata, BitmapMetadata)
    5. Dim date1 = DateTime.Parse(metadata.DateTaken)
    6. Dim bitmap1 As New Bitmap(data)
    7. Dim text1 As String = metadata.Comment & ", " & date1.ToString("dd.MMMMMMMMMMMMMMMMMMMM yyyy")
    8. 'Textgröße 3% der Bildhöhe
    9. Dim fsize As Int32 = CInt(bitmap1.Height * 0.03)
    10. Dim font1 As New Font("Arial", fsize, FontStyle.Regular, GraphicsUnit.Pixel)
    11. Using g As Graphics = Graphics.FromImage(bitmap1)
    12. Dim size1 = g.MeasureString(text1, font1).ToSize
    13. Dim GP As New GraphicsPath
    14. GP.AddString(text1, font1.FontFamily, font1.Style, font1.Size, New Point(bitmap1.Width - size1.Width, bitmap1.Height - size1.Height), New StringFormat)
    15. g.SmoothingMode = SmoothingMode.HighQuality
    16. g.PixelOffsetMode = PixelOffsetMode.HighQuality
    17. Dim GPOutline As GraphicsPath = CType(GP.Clone, GraphicsPath)
    18. GPOutline.Widen(New Pen(Brushes.Black, 4))
    19. g.FillPath(Brushes.Black, GPOutline)
    20. g.FillPath(Brushes.White, GP)
    21. GPOutline.Dispose()
    22. GP.Dispose()
    23. End Using
    24. Debug.WriteLine(bitmap1.PropertyItems.Length)' Wenn ich diese Zeile auskommentiere, dann sind die Propertyitems weg
    25. data.Close()
    26. Debug.WriteLine(bitmap1.PropertyItems.Length)
    27. Return bitmap1
    28. End Function


    Output bei auskommentiert:
    0

    Output sonst:
    46
    46

    Im Release Modus sind die Propitems immer weg.
    Das ganze läuft in nem BackGroundWorker, daran liegts aber nicht (hab grad ein leeres Projekt NUR mit dieser Funktion erstellt -> selbes Ergebnis).
    Wenns jemand nachvollziehen will: Man braucht folgende Verweise:
    PresentationCore
    System.Xaml
    WindowsBase

    Dieser Beitrag wurde bereits 3 mal editiert, zuletzt von „markus.obi“ ()

    markus.obi schrieb:

    Es soll ja keine Machine lesen, sondern ein Mensch.
    Selbstverständlich.
    Meine Intention war, einen Automaten die Beschriftung erstellen zu lassen, der hätte Aufwand bei einer Kontrastmaximierung.
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    Ich hab das Problem mit den PropertyItems gelöst.
    Beim Erstellen der Bitmap aus dem MemoryStream sagt MSDN: "Der Stream muss für die Lebensdauer der Bitmap geöffnet bleiben."

    Insofern darf die Bitmap nach dem Schließen nicht mehr verwendet werden. Ich dachte, ich klone die Bitmap einfach, aber denkste!
    Die Propertyitems werden nicht mitgeklont :cursing: . Warum auch immer...
    Man muss nach dem Klonen noch die Propertys setzen.