/*
* Copyright (c) 2015 OpenSilk Productions LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package syncthing.android.identicon;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.Color;
import android.support.v4.content.ContextCompat;
import org.opensilk.common.core.dagger2.ForApplication;
import javax.inject.Inject;
import javax.inject.Singleton;
import rx.Observable;
import rx.Subscriber;
import rx.android.schedulers.AndroidSchedulers;
import rx.schedulers.Schedulers;
import syncthing.android.R;
import timber.log.Timber;
@Singleton
public class IdenticonGenerator {
final int size;
final int scaledSize;
final int color;
@Inject
public IdenticonGenerator(
@ForApplication Context appContext
) {
size = 5;
scaledSize = appContext.getResources().getDimensionPixelSize(R.dimen.identicon_size);
color = ContextCompat.getColor(appContext, R.color.grey_600);
}
public Observable<Bitmap> generateAsync(String userName) {
return Observable.create(new Observable.OnSubscribe<Bitmap>() {
@Override
public void call(Subscriber<? super Bitmap> subscriber) {
Bitmap bitmap;
try {
bitmap = generate(userName);
subscriber.onNext(bitmap);
subscriber.onCompleted();
} catch (Exception e) {
subscriber.onError(e);
}
}
}).subscribeOn(Schedulers.computation()).observeOn(AndroidSchedulers.mainThread());
}
public Bitmap generate(String deviceId) {
Identicon identicon = new Identicon(deviceId, size, scaledSize);
return identicon.createBitmap(color);
}
/**
* creates an identicon (almost) equivalent to the one created by the identiconDirective in syncthing.core
*/
private static class Identicon {
final String value;
final int size;
final int scaledSize;
final int middleCol;
public Identicon(String value, int size, int scaledSize) {
this.value = value.toUpperCase().replaceAll("[\\W_]", "");
this.size = (size % 2) == 1 ? size : size + 1; //must be odd;
this.scaledSize = scaledSize;
this.middleCol = (this.size - 1) / 2; //0 based idx
}
private boolean shouldFillRectAt(int row, int col) {
int idx = row + col * size;
while (idx >= value.length()) {
//wrap around
idx -= value.length();
}
return (Character.codePointAt(value, idx) % 2) == 0;
}
private boolean shouldMirrorRectAt(int row, int col) {
return col != middleCol;
}
private int mirrorColFor(int col) {
return size - col - 1;
}
public Bitmap createBitmap(int foreground) {
int background = Color.TRANSPARENT;
Bitmap identicon = Bitmap.createBitmap(size, size, Config.ARGB_8888);
for (int row = 0; row < size; row++) {
for (int col = 0; col <= middleCol; col++) {
int pixelColor = shouldFillRectAt(row, col) ? foreground : background;
identicon.setPixel(col, row, pixelColor);
if (shouldMirrorRectAt(row, col)) {
identicon.setPixel(mirrorColFor(col), row, pixelColor);
}
}
}
Bitmap scaledIdenticon = Bitmap.createScaledBitmap(identicon, scaledSize, scaledSize, false);
if (scaledIdenticon != identicon) {
identicon.recycle();
}
return scaledIdenticon;
}
}
}