# Monday, January 09, 2006
In Ask Jeeves - neu und besser als Google? hat Günter Ratz auf der deutschen Beta von Ask Jeeves mal nach dem Begriff Technischer Dokumentation gesucht. Und natürlich habe ich da auch mal klicken müssen...

Dabei ist mir eines der neuen Features dieser Suchmaschine aufgefallen. Zu einigen Treffern gibt es ein Fernglas als Icon. Wenn man mit der Maus darüber fährt, wird ein kleine, gespeicherte Voransicht der Webseite geladen und eingeblendet. Sehr nett:



Da freut man sich doch, wenn man optisch so schön gefunden werden kann!

Doch, oh Schreck, was passiert da bei dem Treffer zum Tekom WebPortal?




Ah! Das barrierefreie Web...
Blah | TD
Monday, January 09, 2006 2:39:12 PM (W. Europe Standard Time, UTC+01:00)  #
  Disclaimer  |  Comments [0]  | 
# Thursday, December 01, 2005
Firefox 1.5 ist fertig und natürlich testen wir unsere wichtigste Web-Applikation SQLCat mit der neuesten Version dieses beliebten Browsers. Soll ja alles weiter funktionieren. IE kann und will man ja nicht mehr voraussetzen.

Aber was sieht der entsetzte Entwickler? Fehler in der JavaScript Console.
Error: Expected ':' but found '='.  Declaration dropped.
Oh oh! Jetzt ist aber ernsthaftes Debuggen angesagt! Dummerweise stimmt aber alles. Zumindest mit dem eingebauten JavaScript. Eine verzweifelte Stunde später löst sich das Problem: Nicht im Skripting sondern im Styling steckt der Wurm. Es war ein CSS Fehler! Auch noch ein ganz nachvollziehbarer. Entwickler wollen mit "=" Variablen zuweisen:
<table style="border-style=solid; border-color=white; border-width=0; border-left-width=3; border-right-width=3;" width="100%" cellspacing="0" cellpadding="1" bgcolor="white">
Da gehört in den Inline-Style natürlich ein ":" statt dem "=" rein. Das sagt auch die Fehlermeldung in der Konsole. Nur von CSS erwähnt sie nichts. Schade eigentlich.



Ansonsten ist dieses neue Feature natürlich praktisch, wenn die Fehlermeldung noch deutlicher als CSS Fehler gekennzeichnet wird.
In der Trefferliste von google.de steckt witzigerweise der gleiche Fehler :).

Thursday, December 01, 2005 5:17:23 PM (W. Europe Standard Time, UTC+01:00)  #
  Disclaimer  |  Comments [0]  | 
# Friday, November 25, 2005

Es gibt einige kleine Tools, die wir uns für den internen Gebrauch selber basteln. Oft gibt es keine passende fertige Lösung. Manchmal ist das entsprechende Produkt auch zu teuer. Das war auch der Fall, als wir unsere Backup-Lösung überdachten. Festplatten werden immer billiger, aber Bandlaufwerke und die passenden Bänder nicht in dem gleichen Masse. Wechselmedien sollten es aber schon geben, schließlich sollen die wichtigen Daten an einem entfernten Standort gelagert werden. Wir haben uns deshalb für eine Datensicherung auf USB-Festplatten entschlossen.

Natürlich gibt es Backup-Software, die auf externe Platten sichern kann. Wir hatten aber weitergehende Ansprüche:
  • Unser Vertrauen in IDE-Festplatten, insbesondere solche, die häufig transportiert werden, ist nicht sehr groß. Also wird alles auf 2 Platten gesichert.
  • Der Platz auf den Platten soll optimal genutzt werden. Alte Daten bleiben solange wie möglich auf der Platte. Sie werden erst automatisch gelöscht, wenn der Platz für die aktuellen Daten wirklich benötigt wird.
  • Als Kompromiss zwischen Redundanz und Verwahrungszeit der Daten soll automatisch bei jedem Backup die die Zielplatte gewechselt werden können.
  • Alle kopierten Daten werden in eine Datenbank protokolliert. Mit einfachen SQL Befehlen kann man gesicherte Dateien aufspüren.
  • Die Berechtigungen der Quelldateien sollen auf die Zieldateien übertragen werden. Dadurch ist ein Rücksichern der Daten einfacher und man kann den Anwendern über eine Readonly-Freigabe Zugriff auf die Sicherungen geben.
  • Fehler oder Erfolgsmeldungen werden in das Event Log geschrieben und per E-Mail verschickt.
Inzwischen läuft dieses "CPTec Backup" seit einiger Zeit und wir sind mit dem Ergebnis sehr zufrieden. Ein paar kleine Änderungen und Optimierungen an der Software und der Konfiguration gab es natürlich auch zwischendurch. Die Konfiguration ist als XML-Datei abgelegt und der Backup Job wird mit dem Taskplaner gestartet. Alles ganz einfach. Täglich kamen E-Mails, die den Erfolg der Inkrementellen Sicherung verkündeten. Prima!

Drei CPTec-Mitarbeiter bekamen diese E-Mails. Keinem ist es aufgefallen, als über ein Woche lang keine Meldung mehr kam. Vor lauter Korrespondenz und Spam fällt es nicht auf, wenn eine tägliche Mail nicht ankommt. Was war passiert? Ich hatte eine minimale Änderung an der Konfiguration vorgenommen. Als Entwickler der Software macht man so was auch ganz selbstverständlich. Ich weiß ja was ich mache. Denkste! Die Datei war kein valides XML mehr. CPTec Backup hat das natürlich dem Event Log anvertraut. Aber woher sollte es ohne Konfigurations-Datei wissen, wohin die E-Mails geschickt werden sollen? Kann es nicht, macht es nicht.

Zum Glück ist nur das Inkrementelle Backup betroffen gewesen. Die Komplettsicherung lief separat und ungestört weiter.

Natürlich will man aus Fehlern lernen. Die erste Konsequenz ist, dass CPTec Backup jetzt einen Kommandozeilen-Parameter zum Testen der Konfiguration bekommen hat. Ohne dass tatsächlich eine Sicherung stattfindet, wird die Datenbank getestet und eine Test E-Mail versendet. Zusätzlich wird auch vor dem Start der eigentlichen Sicherung eine E-Mail verschickt. Um die Auswirkung einer überquellenden Inbox zu lindern sortiere ich in Outlook auch noch alle Backup-Meldungen anhand des Absenders in einen separaten Ordner. Jetzt sind wir hoffentlich vor meinen Tippfehlern besser geschützt.

Und bevor ich es vergesse: Wir haben uns dazu entschlossen, auch anderen Interessenten diese Backup-Software zur Verfügung zu stellen. Die Anwendung ist mit C# unter .Net 1.1 entwickelt. Sogar der Quelltext ist erhältlich. Wer sich dafür interessiert, kann auf unter CPTec Software mehr dazu lesen :).

Friday, November 25, 2005 4:50:57 PM (W. Europe Standard Time, UTC+01:00)  #
  Disclaimer  |  Comments [0]  | 
# Thursday, November 17, 2005

Google Earth?
Nasa World Wind?
MSN Virtual Earth?
map24.de?

Ach was. Meine interaktive Karte ist noch von Anfang 2004. Damals bin ich mit meiner damals ganz toll neuen Digi-Cam in die Arbeit geradelt und habe ein paar Fotos gemacht:
Mein Arbeitsweg

:)
Thursday, November 17, 2005 5:11:18 PM (W. Europe Standard Time, UTC+01:00)  #
  Disclaimer  |  Comments [0]  | 
# Wednesday, November 16, 2005

Ich habe bereits in Ersatzteilkatalog - Bildnavigation mit GDI+ erzählt, dass ich versuche in Visual Foxpro 9 mit GDI+ direkt auf VFP Formen zu zeichnen. Inzwischen funktioniert das eigentlich ganz gut. Einige Probleme waren noch zu lösen und den Bildaufbau habe ich noch optimiert.

In Craig Boyd's Artikelserie in FoxTalk 2.0 wird eine komplette Form mit GDI+ bearbeitet. In unserer SPCat Applikation ist aber nur ein Bereich der Form für die Darstellung der Bilder genutzt. Der restliche Bereich der Form soll weiterhin von Foxpro selbst gezeichnet werden. Deswegen wollte ich die ganze Bilddarstellung in eine Container Klasse verpacken und die eigentliche Form so wenig wie möglich verändern.

Die eigentlichen GDI+ Zeichenroutinen stelle ich nicht vor. Es geht mir jetzt mehr darum wie ich diese Routinen mit den Mechanismen von VFP verheiratet habe, damit Foxpro und GDI+ Elemente auf einer Form dargestellt werden. Foxpro weiß ja nichts davon, dass man ihm mit eigenen Routinen in die Bildschirmdarstellung pfuscht. Deswegen wird es bei jedem Update des Bildschirminhalts einfach seine eigenen Elemente zeichnen. Mit BINDEVENT hat man die Möglichkeit in die Nachrichten von Windows an das Foxpro Fenster zum Neuzeichnen des Inhalts einzugreifen. Hiermit werden die relevanten Events auf die eigene Prozedur WinEventHandler umgeleitet:

#DEFINE WM_ERASEBKGND 0x0014
#DEFINE WM_PAINT 0x000F

=BINDEVENT(THISFORM.hwnd, WM_ERASEBKGND, THIS, 'WinEventHandler')
=BINDEVENT(THISFORM.hwnd, WM_PAINT, THIS, "WinEventHandler")

Wir brauchen auch noch zwei Aufrufe in die Windows API:

DECLARE LONG CallWindowProc IN User32 ; INTEGER lpPrevWndFunc, LONG HWND, ; INTEGER Msg ,INTEGER wParam, ; INTEGER lParam
DECLARE INTEGER GetUpdateRect IN User32 LONG HWND, STRING @lpRect, INTEGER bErase

CallWindowProc ruft die Routine auf, die normalerweise den WM_PAINT Event behandelt. Also in diesem Fall die interne Foxpro Funktion, die dessen Elemente zeichnet.
GetUpdateRect liefert das Rechteck innerhalb dessen etwas zu zeichnen ist. Damit kann man den Einsatz der eigenen Zeichenroutinen auf die benötigten Fälle reduzieren. Dieses Rechteck ist als folgendes Struct definiert:

typedef struct _RECT { 
LONG left;
LONG top;
LONG right;
LONG bottom;
} RECT, *PRECT;

In der WinEventHandler Prozedur werden die Windows Nachrichten bearbeitet:

PROCEDURE WinEventHandler
LPARAMETERS HWND, msg, wParam, LPARAM

#DEFINE WM_ERASEBKGND 0x0014
#DEFINE WM_PAINT 0x000F

DO CASE
    CASE msg = WM_PAINT
        WITH THIS
            IF .autodraw
                LOCAL lpRect,lnUpdate
                lpRect=SPACE(16)
                * Das Rechteck holen, in dem gezeichnet werden muss.
                lnUpdate=GetUpdateRect(HWND, @lpRect, 0)
                * Dieser Aufruf führt dazu, dass VFP alles zeichnt, wofür es zuständig ist.
                CallWindowProc(.WinProcAddress, HWND, msg, wParam, lParam)
                IF lnUpdate=1
                    * In hitdetection() wird geprüft, ob dieses Control im zu zeichnenden Rechteck liegt.
                    IF .hitdetection(lpRect)
                        .drawdb()
                    ENDIF
                ENDIF
            ELSE
                * Hier ist nur VFP für das Zeichnen verantwortlich.
                CallWindowProc(.WinProcAddress, HWND, msg, wParam, lParam)
            ENDIF
        ENDWITH
    CASE msg = WM_ERASEBKGND
        RETURN 1
ENDCASE

Die Nachricht zum Löschen des Hintergrunds WM_ERASEBKGND wird komplett ignoriert. Mit WM_PAINT treffen alle relevanten Zeichnungsanforderungen ein. Mit der autodraw Eigenschaft kann ich die eigenen GDI+ Routinen an- und ausschalten. Das ist notwendig, da das Control auch unsichtbar sein kann. Dann dürfen natürlich meine GDI+ Routinen nicht auf der Form zeichnen. Bei der Behandlung des WM_PAINT Events darf zuerst Foxpro seine Aufgabe erledigen. Danach teste ich, ob der Container im zu zeichnenden Bereich liegt und starte entsprechende meine Zeichenroutine. Diese Reihenfolge ist wichtig, damit am Ende auch meine Ausgabe zu sehen ist. Sonst würde ja Foxpro den tatsächlichen Container darüber malen.

Damit kann ich jetzt grundsätzlich auf die Fläche des Containers zeichnen und zusätzlich auf der Form weitere Foxpro Elemente anzeigen. Ich möchte aber den Container und damit die GDI+ Zeichnung unsichtbar machen können. Genau genommen möchte ich an der Stelle des Containers auch andere Objekte anzeigen können. Das läuft aber auch auf einen unsichtbaren Container hinaus.
Natürlich kann ich nicht einfach die Visible Eigenschaft auf .F. stellen. Dann würde zwar Foxpro den leeren Container nicht mehr zeichnen, aber meine Routinen würden munter weiter zeichnen. Über visible_assign werden bei Änderungen der Sichtbarkeit auch meine Zeichenroutinen mit umgestellt.

Es hat sich aber gezeigt, dass VFP seltsamerweise Teile des Containers zeichnet wenn Visible auf .T. gesetzt, obwohl der Container bereits sichtbar ist. Das führt leider zu einem Flackern. Da ich ja selbst erst nach Foxpro zeichne und damit darunterliegende Objekte kurz aufblitzen. Also wird dieser Fall auch in visible_assign abgefangen:

PROCEDURE visible_assign
LPARAMETERS vNewVal
WITH THIS
    IF m.vNewVal
        IF .Visible
            * Wenn das Control schon sichtbar ist, nicht nochmal auf sichtbar schalten.
            * Andernfalls würde VFP das Control zeichnen und es flackert.
            .clearborder()
        ELSE
            * Die eigene GDI+ Zeichenroutine anschalten.
            .redraw()
            .Visible=.T.
        ENDIF
    ELSE
        * Die eigene GDI+ Zeichenroutine abschalten.
        .autodraw=.F.
        .Visible=.F.
    ENDIF
ENDWITH

Es kann sein, dass das Bild, das mit GDI+ gezeichnet wird, kleiner als der Container ist. In WM_PAINT Routine wird aus Performance Gründen nur dieser Teil gezeichnet. Damit das Rest des Containers in der Hintergrundfarbe erscheint, lösche ich diesen Bereich von Hand mit clearborder(). Da bei einem Wechsel des Bildes auch die Visible Eigenschaft auf .T. gesetzt wird, ist das die einzige Stelle, wo dieser Rand manuell gelöscht werden muß.

Insgesamt bin ich mit dem Ergebnis jetzt sehr zufrieden. Leider feuert aus mir unbekannten Gründen unregelmässig ein WM_PAINT Event, der offensichtlich grundlos die ganze Form neu zeichnen lässt. Das führt zu einem Flackern. Insbesondere auf langsameren Rechner kann das stören. Trotzdem bin ich beeindruckt, was mit VFP 9, GDI+ und ein paar Tricks möglich ist.

Wednesday, November 16, 2005 8:55:56 PM (W. Europe Standard Time, UTC+01:00)  #
  Disclaimer  |  Comments [0]  | 
# Friday, November 11, 2005

Es kommt ja öfters vor, dass man schnell mal einen Screenshot machen will: Den neuesten Dialog per E-Mail verschicken oder die unerwartete Fehlermeldung dem Entwickler zukommen lassen.
Dafür gibt es diverse kleine Helferlein. Zur Not gibt es auch noch Alt+PrtScn wenn es ganz minimalistisch sein soll.

Aber oft ist dann der Weg zu einer sinnvoll benannten Bild-Datei doch eine größere Unterbrechung als geplant. Wieder ein Hot-Key, den man genau dann vergessen hat. Welche Taste war jetzt für den ganzen Bildschirm, welche für das aktive Fenster? Und wenn dann das Fenster nicht rechteckig ist, wie machen wir das mit dem Rand?

Kenny Kerr hat uns mit Window Clippings 1.0 um ein weiteres Tool bereichert. Wenn man auf das Toolbar Icon klickt, wird der ganze Bildschirm grau, man wählt das gewünschte Fenster aus und es wird hell. Mit einem Doppelklick landet das Fenster wahlweise im Clipboard und in einem History Verzeichnis. Mit der rechten Maustasten kann man die Aktion auswählen. Der Dateiname wird dabei aus Namen des Fensters abgeleitet.

Besonderen Wert hat der Autor dabei auf die Transparenz gelegt. Der Bereich des erzeugten rechteckigen Bildes, der nicht zum Fenster gehört, wird mit transparent gespeichert.
Die Unterstützung für OneNote ist für mich im Moment nicht wichtig. Mit diesem elektronischen Notizbuch bin ich nicht warm geworden.

Ein kleines Tool, daß seinen Platz in meiner Sammlung gefunden hat.
Friday, November 11, 2005 2:48:44 PM (W. Europe Standard Time, UTC+01:00)  #
  Disclaimer  |  Comments [0]  | 
# Monday, November 07, 2005

Jetzt beginnen die lustigen Spielchen mit dem neuen .NET 2.0 Framework. Ich möchte ein internes Tool auf die neue Version umstellen. Beim Kompilieren kommen nur noch Warnings, keine Errors, also mal fröhlich F5 gedrückt, um die Applikation im Debugger zu starten. Haste wohl gedacht. Visual Studio 2005 überrascht mit folgender Meldung:

Error while trying to run project: Unable to start debugging.
The binding handle is invalid.

Na prima! Was ist da kaputt? Im Microsoft Forum habe ich diesen Thread gefunden: Unable to debug: The binding handle is invalid
Damit konnte ich das Problem lösen. Wobei mich aber die Lösung schon sehr irritiert hat:

Vor einiger Zeit bin ich mal die Dienste (Services) auf meinem Rechner durchgegangen und habe alles ausgeschaltet, was ich wirklich nicht gebraucht habe. Wofür brauche ich auf meinem Desktop Rechner zum Beispiel die Terminal Services? Fast User Switching will ich nicht und Remote Zugriff gibt es auch nicht. Also stand der Autostarttyp (Startup type) auf Disabled. Jetzt lerne ich, dass man deswegen nicht mit Visual Studio 2005 lokal debuggen kann.

Was da intern alles abläuft, möchte ich gar nicht wissen. Brrr. Wenn der Terminal Services Dienst auf Manual steht, dann startet der Dienst, sobald VS 2005 startet. Danach geht auch das Debugging.

Wichtig dabei ist noch, das man den Rechner neu starten muss, nachdem man den Autostarttyp geändert hat

Zusammengefasst:
  1. Visual Studio 2005 will nicht debuggen.
  2. Control Panel -> Administrative Tools -> Services -> Terminal Services -> Startup type -> Manual.
  3. Reboot.
  4. Visual Studio 2005 ist zufrieden, ich bin verwundert.

Monday, November 07, 2005 5:14:57 PM (W. Europe Standard Time, UTC+01:00)  #
  Disclaimer  |  Comments [1]  | 
# Friday, November 04, 2005

Mit .NET 1.1 ist nicht direkt möglich eine kontextsensitive HTML Hilfe (CHM-Datei) mit einer ganzzahligen Topic ID aufzurufen. Die HelpNavigator Aufzählung definiert die verschiedenen Möglichkeiten eine bestimme Stelle in der Hilfe anzuspringen. Leider ist keine Topic ID dabei, obwohl die HTML Hilfe diese unterstützen würde.
Eine kurze Recherche hatte damals eine  Lösung geliefert. Das FrameWork gibt den HelpNavigator Wert als ganze Zahl an die eigentlich Hilfe Darstellung weiter. Ein Wert von 15 erlaubt eine ganzzahlige Topic ID. Ein Cast auf HelpNavigator und siehe da, es geht.

Die Lösung sah dann im Wesentlichen so aus:

public static void ShowHelpTopic(System.Windows.Forms.Control parent, int topicID)
{
    string helpFile = @"c:\\help.chm";
    System.Windows.Forms.Help.ShowHelp(parent, helpFile, (System.Windows.Forms.HelpNavigator)15, topicID);
}

Jetzt, da das .NET Framework 2.0 erschienen ist, habe ich getestet, ob die Applikation direkt unter dieser Version funktioniert. Also ohne ein .Net 1.1 vorauszusetzen. Es hat eigentlich alles funktioniert, bis auf die kontextsensitive Hilfe. In Version 2.0 erlaubt HelpNavigator nämlich eine Topic ID. Der übergebene Parameter ist dann aber kein ganze Zahl, sondern die ID als Zeichenkette.
Da die Applikation auch weiterhin unter .Net 1.1 kompilierbar sein soll, kann die Topic ID nicht direkt aus der HelpNavigater Aufzählung verwendet werden. Also verwende ich wieder die Entsprechung dieses Wertes als ganze Zahl. Über eine Fallunterscheidung werden dann in Abhängigkeit von dem verwendeten .NET Framework die passenden Parameter übergeben. Damit funktioniert die HTML Hilfe auch unter .Net 2.0 kontextsensitiv.

Vereinfacht sieht die neue Methode dann so aus:

public static void ShowHelpTopic(System.Windows.Forms.Control parent, int topicID)
{
    string helpFile = @"c:\\help.chm";

    if (System.Runtime.InteropServices.RuntimeEnvironment.GetSystemVersion().Substring(1,3) == "1.1")
    {
        // .NET 1.1
        System.Windows.Forms.Help.ShowHelp(parent, helpFile, (System.Windows.Forms.HelpNavigator)15, topicID);
    }
    else
    {
        // .NET 2.0
        System.Windows.Forms.Help.ShowHelp(parent, helpFile, (System.Windows.Forms.HelpNavigator)(-2147483641), topicID.ToString());
    }
}

Mit dieser Erweiterung können unsere Applikationen jetzt kontextsensitive HTML Hilfe unter beiden .NET Versionen darstellen.
Als nächstes werde ich damit beginnen unser LinkEdit, eine Applikation zum Setzen von HotSpots für SPCat, auf .NET 2.0 umzustellen. Dann kann ich ganz einfach nur HelpNavigator.TopicID verwenden. Wie langweilig :).

Friday, November 04, 2005 2:53:37 PM (W. Europe Standard Time, UTC+01:00)  #
  Disclaimer  |  Comments [0]  | 
# Thursday, October 27, 2005

Kein Vergleich der Funktionalität, sondern der Performance und des Speicherbedarfs von Microsoft Office 2003 im Vergleich zu Open Office 2.0:

Performance analysis of OpenOffice and MS Office by ZDNet's George Ou

Au Backe, da sieht Open Office 2.0 aber richtig schlecht aus. Es braucht deutlich länger und verwendet mehr Speicher.

Vielleicht findet sich ja die Zeit, daß ich selber mal damit rumspiele. Technische Dokumentation und Open Source geistert bei uns schon länger als Thema rum. Unter dem Gesichtspunkt könnte Open Office interessante Möglichkeiten bieten.

Admin | TD
Thursday, October 27, 2005 2:27:58 PM (W. Europe Daylight Time, UTC+02:00)  #
  Disclaimer  |  Comments [1]  | 
# Wednesday, October 26, 2005

Die meisten Zeichnungen, die im SPCat verwendet werden sollen, liegen als Schwarz-Weiß-Grafik im Tiff-Format vor. Um diese Grafiken aufzubereiten habe ich in C# eine .NET Applikation "TiffSPCat" geschrieben. Unter anderem werden dabei Gif-Grafiken für die Bildschirmdarstellung erzeugt. Da in der Ausgangs-Grafik keine Farben vorkommen, werden die Gif-Grafiken mit einer festen Palette von 256 Graustufen erzeugt.
Inzwischen ist TiffSPCat deutlich flexibler und erlaubt auch die Konvertierung anderer Pixel-Formate. Diese müssen nicht notwendig in Schwarz-Weiß oder als Graustufen vorliegen. Also kann ich nicht mit einer Graustufen-Palette bei den Gifs arbeiten.
Der naheliegende Weg wäre also, einfach im System.Drawing.Imaging Namensraum den Gif Encoder auszuwählen und damit auf der erzeugten Bitmap die Save Methode aufzurufen. GDI+ wird es schon richten.
Bei Ausgangs-Grafiken mit wenigen Farben, also einer typischen technischen Zeichnung, sieht das Ergebnis passabel aus und ich war zunächst zufrieden.
Leider störte dann bei der weiter Verwendung der Gif-Grafiken deren Transparenz. Bei den Grafiken mit der selbst erzeugten, festen Graustufen-Palette waren die Gif-Grafiken nicht transparent. Also doch weiter in den Eingeweiden von GDI+ und dem .NET Wrapper System.Drawing.Imaging stöbern.

Als erstes fällt auf, daß immer eine Standard-Palette mit 256 Farben verwendet wird. Die Palette kann man in der GDI+ FAQ Creating Transparent GIF Images von Bob Powell schön sehen. Es gibt keine Möglichkeit über zusätzliche Parameter eine optimierte, adaptive Palette erzeugen zu lassen. Schade, also von Hand die Palette erzeugen. Aber erstmal Google fragen, keine Räder neu erfinden.
Das Beispiel aus diesem MSDN Artikel Optimizing Color Quantization for ASP.NET Images quantisiert mit einem Octree Algorithmus die Echtfarben auf eine optimierte Palette. Mit ein paar kleinen Veränderungen konnte ich den Quelltext in TiffSPCat einbauen. Die erzeugten Paletten sehen gleich viel sinnvoller und die dazugehörigen Gif-Grafiken gleich viel besser aus.

Trotzdem blieb eine Farbe in dem erzeugten Gif transparent. Jetzt habe ich aber Kontrolle über die verwendete Palette. Der letzte Schritt klärt sich dann mit diesem Knowledge Base Artikel KB319061, How to save a .gif file with a new color table by using Visual C# .NET. Die erste Farbe in der Palette mit einem Alpha Wert gleich Null, wird transparent. Also alle Alpha Werte auf 255 setzen:

// Palette von der Bitmap holen.
ColorPalette pal = this.activeBitmap.Palette;

// Alpha auf 255 setzen.
for(int i = 0; i < pal.Entries.Length; i++)
{
Color col = pal.Entries[i];
pal.Entries[i] = Color.FromArgb(255, col.R, col.G, col.B);
}

// Palette zuweisen.
this.activeBitmap.Palette = pal;

Die fertige Bitmap im PixelFormat.Format8bppIndexed und allen Alpha Werten ungleich Null wird dann mit dem Gif Encoder von GDI+ ohne Transparenz gespeichert.

Wednesday, October 26, 2005 11:18:36 AM (W. Europe Daylight Time, UTC+02:00)  #
  Disclaimer  |  Comments [0]  |