Android dynamische Bilder erstellen
Ich zeige euch wie man unter Android dynamische Bilder erstellen kann. Das selbe hatte ich bereits vor einigen Wochen in PHP entwickelt. Da es sich um eine Client/Server App handelt und ich den Server soweit wie möglich entlasten will macht es sinn, dass die Icons im besten Fall direkt am Client (Android) erstellt werden. Serverseitig (PHP) werden diese Bilder nur in automatischen Tasks einer KI erstellt.
Android dynamische Bilder erstellen
Das wichtigste unter Android ist Performance. Ladezeiten von mehreren Sekunden führen dazu, dass der Kunde die App beendet und nie wieder startet. Aus diesem Grund müssen länger dauernde Tasks unter Android in eigene Threads ausgelagert werden. Ich verwende das bei meiner neuen App beispielsweise um Daten über http:// von einer API eines Webservices abzufragen.
Die Aufgabenstellung ist folgende: der Benutzer soll sich in einem Editor mit einfacher Bedienung ein Bild aus verschiedenen Mustern und Icons in mehreren Layern zusammenstellen. Um den Leistungsumfang zu erhöhen kann der Benutzer auch mehrere Farben auswählen. Das funktioniert indem alle Muster in Schwarz/Weiß hinterlegt sind und bei der Erstellung des Bildes eingefärbt werden. Ein Beispiel wie ein fertiges Bild aus mehreren Layern mit Farben erstellt wird:
Ablauf
Das Bild zeigt sehr schön wie das fertige Bild aufgebaut sehr könnte. Zuerst wählt der Benutzer ein Muster, hier einfach zwei Farben mit der Trennung in der Mitte. Danach wählt man eine Form. Zusätzlich kann man noch Details setzen (das wäre in diesem Fall der Fußballer). Das Bild besteht aus 3 Layer und man kann 3 unterschiedliche Farben (2 für das Muster und eine für die Details) wählen.
Performanceproblem
Ich habe die selbe Funktionalität Server-seitig mit PHP umgesetzt. Dabei habe ich einfach für die einzelnen Layer das Bild Pixel für Pixel bearbeitet und Farbe und Alphawert gesetzt. Das selbe funktioniert auch unter Android, das dauert aber je nach Gerät 5 Sekunden oder länger. Eine lange Zeit in der ein Benutzer gar keine Interaktivität hat. Alternativ kann man das in einen Thread auslagern, jedoch würde man trotzdem Sekunden warten bis das Bild nachgeladen wird.
Lösung
Nach langer Recherche und zahlreichen Tests hat mich ein GitHub Projekt zur Lösung gebracht. Dort wird gezeigt wie man Shader effektiv zur Bildmanipulation einsetzt und mehrere Layer miteinander verknüpfen kann. Für mich war folgender Source Code zielführend.
Immer wenn durch eine Benutzerinteraktion (neue Farbe, anderes Muster) das Bild geändert wird wird die Funktion refreshBitmap() ausgeführt:
public void refreshBitmap() { BitmapFactory.Options o = new Options(); o.inScaled = false; o.inMutable = true; this.bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.pattern01, o); this.bitmap_mask = BitmapFactory.decodeResource(getResources(), R.drawable.shape01, o); this.bitmap_overlay = BitmapFactory.decodeResource(getResources(), R.drawable.overlay01, o); //change bitmap color int [] allpixels = new int [this.bitmap.getHeight()*this.bitmap.getWidth()]; this.bitmap.getPixels(allpixels, 0, this.bitmap.getWidth(), 0, 0, this.bitmap.getWidth(), this.bitmap.getHeight()); for(int i = 0; i < allpixels.length; i++) { if(allpixels[i] == Color.BLACK) { allpixels[i] = Color.RED; } if(allpixels[i] == Color.WHITE) { allpixels[i] = Color.BLUE; } } this.bitmap.setPixels(allpixels, 0, this.bitmap.getWidth(), 0, 0, this.bitmap.getWidth(), this.bitmap.getHeight()); //change overlay color allpixels = new int [this.bitmap_overlay.getHeight()*this.bitmap_overlay.getWidth()]; this.bitmap_overlay.getPixels(allpixels, 0, this.bitmap_overlay.getWidth(), 0, 0, this.bitmap_overlay.getWidth(), this.bitmap_overlay.getHeight()); for(int i = 0; i < allpixels.length; i++) { if(allpixels[i] == Color.BLACK) { allpixels[i] = Color.YELLOW; } } this.bitmap_overlay.setPixels(allpixels, 0, this.bitmap_overlay.getWidth(), 0, 0, this.bitmap_overlay.getWidth(), this.bitmap_overlay.getHeight()); }
Es werden die Bilder der 3 Layer geladen (bitmap, bitmap_mask un bitmap_overlay). In der ersten Schleife wird die Farbe Pixel für Pixel verändert. Schwarz auf die erste Farbe (hier fix mit Rot) und Weiß mit der zweiten Farbe (hier fix Blau) geändert wird. Die Funktion setPixels() kann ein Bitmap sehr schnell ändern, wenn man ein Array aus Pixel übergibt.
Das selbe machen wir mit dem Overlay, ich ersetze Schwarz mit einer dritten Farbe (hier fix Gelb).
Bild zeichnen
In der onDraw() Methode müssen wir nun nur noch das Bild aus den Layer zusammenbauen.:
protected void onDraw(Canvas canvas) { super.onDraw(canvas); Paint maskPaint = new Paint(Paint.ANTI_ALIAS_FLAG); maskPaint.setDither(true); maskPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN)); canvas.drawBitmap(this.bitmap, 0, 0, null); canvas.drawBitmap(this.bitmap_mask, 0, 0, maskPaint); maskPaint.setXfermode(null); canvas.drawBitmap(this.bitmap_overlay, 0, 0, null); //this has to be set otherwise transparent pixel are black setLayerType(LAYER_TYPE_HARDWARE, maskPaint); }
Wichtig ist in diesem Zusammenhang die Konstante PorterDuff.Mode.DST_IN durch die bestimmt wird wie mit der Maske umgegangen wird. Es wird zuerst das Muster gezeichnet, danach mit der Maske das Muster bearbeitet und zuletzt noch das Overlay darüber gezeichnet. Die canvas Methoden sind extrem schnell, das Bild erscheint umgehend.
Fazit
In diesem Tutorial habe ich euch gezeigt wie man unter Android dynamische Bilder erstellen kann. Diese Methode ist recht praktisch wenn der Benutzer einer App dynamische Bilder erstellen können soll. Ideal ist das für eigene Icons, Logos, Wappen oder Profilbilder. Nachdem der Code einmal läuft ist es recht einfach diesen Editor durch zusätzliche Farben, Muster, Layer oder Overlay Bilder zu erweitern.
Findet ihr den Artikel hilfreich? Wofür benötigt ihr unter Android dynamische Bilder?
Bin erst vor kurzen auf RasPi gekommen .Nun habe ich einen 3 B +Zero.Da ich Elektronik Pass 3 habe ist es nicht schwer kleine schaltungen zusamen zu bauen.Thx für die vielen „sehr guten * Erklärungen.Ansonst gibt es die ja nur in sehr teuern Büchern oder nur in schlechten English…
THX
Pierre