Android Open Source - BlobDroidLiveWallpaper Blob Droid Service






From Project

Back to project page BlobDroidLiveWallpaper.

License

The source code is released under:

GNU General Public License

If you think the Android project BlobDroidLiveWallpaper listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.

Java Source Code

package  org.neon.coydog.blobdroid;
// ww w.j ava 2  s  . co  m
import android.service.wallpaper.WallpaperService;
import android.os.Handler;
import android.preference.PreferenceManager;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.graphics.Rect;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Bitmap.*;
import android.graphics.Bitmap;
import android.graphics.Matrix;
import java.lang.Math;
import java.util.Random;
import java.lang.ArrayIndexOutOfBoundsException;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
//import java.lang.Formatter;


public class BlobDroidService extends WallpaperService {
  // TODO: move into prefs?
  //static int FRAMERATE = 120;
  static final int SCALEFACTOR = 4;
  //static final int FRAMERATE = 60;
  //static final int FRAMERATE = 120;
  static final int FRAMERATE = 60;
  @Override
  public Engine onCreateEngine() {
    return new BlobDroidEngine();
  }

  private class BlobDroidEngine extends Engine {
    // TODO: should be an enum;
    static final int FADE_TYPE_NONE = 0;
    static final int FADE_TYPE_RED = 1;
    static final int FADE_TYPE_GREEN = 2;
    static final int FADE_TYPE_BLUE = 3;

    private final Handler handler = new Handler();
    private SurfaceHolder sh = getSurfaceHolder();
    private final Runnable drawRunner = new Runnable() {
      @Override
      public void run() {
        draw();
      }
    };

    class Blob {
      int x, y, vx, vy; // position and vector
    }

    class BlobSettings implements SharedPreferences.OnSharedPreferenceChangeListener {
      // TODO: Should use R.array.*? 
      static final String KEY_PRESET = "preset";
      static final String KEY_TOUCH_TYPE ="touchType";
      static final String KEY_COLOR_CENTER = "colorCenter";
      static final String KEY_COLOR_GLOW = "colorGlow";
      static final String KEY_COLOR_BACKGROUND = "colorBackground";
      static final String KEY_USE_CUSTOM_COLORS = "useCustomColors";
      static final String KEY_NUM_BLOBS = "numberOfBlobs";
      static final String TOUCH_TYPE_NONE = "none";
      static final String TOUCH_TYPE_CENTER = "center";
      static final String TOUCH_TYPE_GLOW = "glow";
      static final String TOUCH_TYPE_BACKGROUND = "background";
      static final String PRESET_DEFAULT = "default";
      static final String PRESET_DOTS = "dots";
      static final String PRESET_ELECTRIC_CHARGE = "electricCharge";
      static final String PRESET_FIRE = "fire";
      static final String PRESET_FLYING_DIRT = "flyingDirt";
      static final String PRESET_GLOW_WORMS = "glowWorms";
      static final String PRESET_GREEN_CONTOUR = "greenContour";
      static final String PRESET_LONELY_BLUE = "lonelyBlue";
      static final String PRESET_NOISE = "noise";
      static final String PRESET_ORANGE = "orange";
      static final String PRESET_WIGGLE = "wiggle";
      static final String PRESET_YELLOW = "yellow";

      // packed ARGB color-ints.
      int colorCenter;
      int colorGlow;
      int colorBackground;
      int numBlobs;
      boolean gravity;
      int blob_size;
      int blob_falloff;
      int blob_presence;
      int border_size;
      int border_falloff;
      int border_presence;
      short multiplication;

      String preset = PRESET_DEFAULT;

      boolean touchEnabledCenter;
      boolean touchEnabledGlow;
      boolean touchEnabledBackground;
      boolean useCustomColors;
      SharedPreferences prefs;

      // constructor just for testing, to play with defaults. Need to construct Colors from 
      // RGB values. TODO: replace with Preferences settings.
      BlobSettings() {
        colorCenter = Color.rgb(255, 255, 255);
        colorGlow = Color.rgb(0, 0, 255);
        colorBackground = Color.rgb(0, 0, 0);
        numBlobs = 5;
        gravity = false;
        blob_size = 2 * SCALEFACTOR;
        //blob_falloff = 16 * SCALEFACTOR;
        blob_falloff = 32;
        blob_presence = 255;
        border_size = 2 * SCALEFACTOR;
        border_falloff = 9;
        border_presence = 128;
        multiplication = 16;

        prefs = PreferenceManager.getDefaultSharedPreferences(BlobDroidService.this);
        prefs.registerOnSharedPreferenceChangeListener(this);

        useCustomColors = prefs.getBoolean(KEY_USE_CUSTOM_COLORS, false);
        preset = prefs.getString("preset", "default");
        applyPreset();
        applyColorOverrides();  // must be after preset
        applyTouchEnabled(prefs.getString(KEY_TOUCH_TYPE, TOUCH_TYPE_NONE));

        // load preset
        // should leave this up to preset or allow override??
        //numBlobs = Integer.valueOf(prefs.getString("numberOfBlobs", 
        //                //"4")); // TODO 4? default?
      }

      protected void applyPreset() {
        // just sets the variables. Call reInit() to rebuild everything based on preset.
        if (preset.equals(PRESET_DEFAULT)) {
          colorCenter = Color.rgb(255, 255, 255);
          colorGlow = Color.rgb(0, 0, 255);
          colorBackground = Color.rgb(0, 0, 0);
          numBlobs = 5;
          gravity = false;
          blob_size = 2 * SCALEFACTOR;
          //blob_size = 4;
          //blob_falloff = 16;
          blob_falloff = 16 * 2; // needs to be based on MAXBLOB_SPAN? unsure exact relationship
          //blob_falloff = BlobUtils.MAXBLOB_SPAN / 2; // needs to be based on MAXBLOB_SPAN
          blob_presence = 255;
          border_size = 2 * SCALEFACTOR;
          border_falloff = 9 * 2;
          border_presence = 128;
          multiplication = 16;
        } else if (preset.equals(PRESET_DOTS)) {
          colorCenter = Color.rgb(0,0,0);
          colorGlow = Color.rgb(0, 191, 255);
          colorBackground = Color.rgb(255, 255, 255);
          //numBlobs = 40; // might have slowdown, maybe adjust
          numBlobs = 40; // might have slowdown, maybe adjust
          gravity = false;
          //blob_size = 2;
          blob_size = 2 * SCALEFACTOR; // tweaked for higher res
          //blob_falloff = 3;
          blob_falloff = (int)(3 * 1.5);
          blob_presence = 32;
          border_size = 0;//2*4;
          border_falloff = 16; 
          border_presence = 127;
          multiplication = 20;
        } else if (preset.equals(PRESET_ELECTRIC_CHARGE)) {
          colorCenter = Color.rgb(0, 0, 0);
          colorGlow = Color.rgb(0, 0, 255);
          colorBackground = Color.rgb(255, 255, 255);
          numBlobs = 50;
          gravity = false;
          //blob_size = 5;
          blob_size = 5 * SCALEFACTOR ;
          //blob_falloff = 32;
          blob_falloff = (int)(32 * 1.5);
          blob_presence = 255;
          border_size = 0;
          border_falloff = 0;
          border_presence = 0;
          multiplication = 64;
        } else if (preset.equals(PRESET_FLYING_DIRT)) {
          colorCenter = Color.rgb(64, 64, 0);
          colorGlow = Color.rgb(128, 128, 0);
          colorBackground = Color.rgb(240, 240, 220);
          numBlobs = 50;
          //gravity = true; // TODO:  gravity not yet implemented.
          gravity = false;
          //blob_size = 0;
          blob_size = 4;
          //blob_falloff = 0;
          blob_falloff = 2;
          blob_presence = 10;
          border_size = 0;
          border_falloff = 64;
          border_presence = 64;
          multiplication = 100;
        } else if (preset.equals(PRESET_FIRE)) {
          colorCenter = Color.rgb(238, 219, 0);
          colorGlow = Color.rgb(255, 82, 0);
          colorBackground = Color.rgb(165, 42, 42);
          numBlobs = 20;
          gravity = false; // TODO: enable gravity when implemented
          blob_size = 3 * SCALEFACTOR;
          blob_falloff = (int)(16 * 1.5);
          blob_presence = 224;
          border_size = 3 * SCALEFACTOR;
          border_falloff = 32;
          border_presence = 128;
          multiplication = 16;
        } else if (preset.equals(PRESET_GLOW_WORMS)) {
          colorCenter = Color.rgb(255, 255, 0);
          colorGlow = Color.rgb(30, 144, 255);
          colorBackground = Color.rgb(0, 0, 0);
          //numBlobs = 20;
          numBlobs = 60;
          gravity = false;
          //blob_size = 0;
          blob_size = 0;
          //blob_falloff = 0;
          blob_falloff = 4;
          blob_presence = 127;
          border_size = 0;
          border_falloff = 0;
          //border_falloff = 2;
          border_presence = 0;
          multiplication = 127;
        } else if (preset.equals(PRESET_GREEN_CONTOUR)) {
          colorCenter = Color.rgb(0, 0, 0);
          colorGlow = Color.rgb(0, 255, 0);
          colorBackground = Color.rgb(0, 0, 0);
          numBlobs = 3;
          gravity = false;
          blob_size = 20 * SCALEFACTOR * 8;
          //blob_size = BlobUtils.MAXBLOB_SPAN;
          //blob_falloff = (int)(16 * 1.5);
          blob_falloff =(int)(16 * 1.5);
          blob_presence = 255;
          border_size = 0;
          border_falloff = 16;
          border_presence = 128;
          multiplication = 24;
        } else if (preset.equals(PRESET_LONELY_BLUE)) {
          colorCenter = Color.rgb(255, 255, 255);
          colorGlow = Color.rgb(0, 0, 255);
          colorBackground = Color.rgb(0, 0, 0);
          numBlobs = 1;
          gravity = false;
          blob_size = 1 * SCALEFACTOR;
          blob_falloff = (int)(16 * 1.5);
          blob_presence = 255;
          border_size = 0;
          border_falloff = 8;
          border_presence = 255;
          multiplication = 64;
        } else if (preset.equals(PRESET_NOISE)) {
          colorCenter = Color.rgb(0, 0, 0);
          colorGlow = Color.rgb(255, 255, 0);
          colorBackground = Color.rgb(173, 216, 230);
          numBlobs = 45;
          gravity = false;
          blob_size = 190 * SCALEFACTOR * 4;
          blob_falloff = 255;
          blob_presence = 255;
          border_size = 0;
          border_falloff = 0;
          border_presence = 0;
          multiplication = 16;
        } else if (preset.equals(PRESET_ORANGE)) {
          colorCenter = Color.rgb(255, 255, 255);
          colorGlow = Color.rgb(255, 165, 0);
          colorBackground = Color.rgb(255, 255, 255);
          numBlobs = 4;
          gravity = false;
          blob_size = 50 * SCALEFACTOR;
          blob_falloff = (int)(16 * 1.5);
          blob_presence = 128;
          border_size = 0;
          //border_size = 4 * SCALEFACTOR;
          //border_falloff = (int)(64 * 1.5);
          border_falloff = 64;
          border_presence = 128;
          multiplication = 0;
        } else if (preset.equals(PRESET_WIGGLE)) {
          colorCenter = Color.rgb(0, 0, 0);
          colorGlow = Color.rgb(0, 191, 255);
          colorBackground = Color.rgb(255, 255, 255);
          numBlobs = 30;
          gravity = false;
          //blob_size = 10;
          //blob_size = 20;
          blob_size = 40;
          //blob_falloff = 20;
          //blob_falloff = 40;
          blob_falloff = 80;
          blob_presence = 64;
          //border_size = 3;
          border_size = 3 * SCALEFACTOR;
          border_falloff = 33;
          //border_falloff = 44;
          border_presence = 128;
          multiplication = 32;
        } else if (preset.equals(PRESET_YELLOW)) {
          colorCenter = Color.rgb(255, 255, 255);
          colorGlow = Color.rgb(255, 255, 0);
          colorBackground = Color.rgb(0, 0, 0);
          numBlobs = 5;
          gravity = false;
          blob_size = 2 * SCALEFACTOR;
          blob_falloff = (int)(20 * 1.5);
          blob_presence = 255;
          border_size = 0;
          border_falloff = 24;
          border_presence = 96;
          multiplication = 8;
        }

        /*SharedPreferences.Editor ed = PreferenceManager.getDefaultSharedPreferences(
            BlobDroidService.this).edit();
        ed.putString(KEY_NUM_BLOBS, Integer.toString(numBlobs));
        ed.apply();*/
        if (BlobDroidEngine.this.utils != null)
          BlobDroidEngine.this.utils.buildColorMap();
      }
      protected void applyPreset (String s) {
        if (preset != s) {
          preset = s;
          applyPreset();
        }
      }
      protected void applyTouchEnabled(String s) {

        touchEnabledCenter = false;
        touchEnabledGlow = false;
        touchEnabledBackground = false;

        if (s.equals(TOUCH_TYPE_CENTER))
          touchEnabledCenter = true;
        else if (s.equals(TOUCH_TYPE_GLOW))
          touchEnabledGlow = true;
        else if (s.equals(TOUCH_TYPE_BACKGROUND))
          touchEnabledBackground = true;
      }

      protected void applyColorOverrides() {
        if (useCustomColors) {
          String prefColorCenter = prefs.getString(KEY_COLOR_CENTER, "").trim();
          String prefColorGlow = prefs.getString(KEY_COLOR_GLOW, "").trim();
          String prefColorBackground = prefs.getString(KEY_COLOR_BACKGROUND, "").trim();

          if (prefColorCenter.length() > 0) {
            try {
              colorCenter = Color.parseColor(prefColorCenter);
            } catch (IllegalArgumentException e) {
            }
          }
          if (prefColorGlow.length() > 0) {
            try {
              colorGlow = Color.parseColor(prefColorGlow);
            } catch (IllegalArgumentException e) {
            }
          }
          if (prefColorBackground.length() > 0) {
            try {
              colorBackground = Color.parseColor(prefColorBackground);
            } catch (IllegalArgumentException e) {
            }
          }

          if (BlobDroidEngine.this.utils != null)
            BlobDroidEngine.this.utils.buildColorMap();
        }
      }

      @Override
      public void  onSharedPreferenceChanged(SharedPreferences p, String key) {
        // TODO: other settings. More touch functionality.
        if (key.equals(KEY_PRESET)) {
          applyPreset(p.getString(KEY_PRESET, PRESET_DEFAULT));
          applyColorOverrides(); // checks flag in applyColorOverrides()

          if (BlobDroidEngine.this.utils != null)
            BlobDroidEngine.this.utils.reInit();

        } else if (key.equals(KEY_TOUCH_TYPE)) {
          String s = p.getString(KEY_TOUCH_TYPE, TOUCH_TYPE_NONE);
          applyTouchEnabled(s);
        } else if (key.equals(KEY_USE_CUSTOM_COLORS)) {
          boolean b = p.getBoolean(KEY_USE_CUSTOM_COLORS, false);
          if (b != useCustomColors) {
            useCustomColors = b;
            if (!useCustomColors){
              applyPreset();
            } else {
              applyColorOverrides();
            }
          }
        } else if (key.equals(KEY_COLOR_CENTER)
            || key.equals(KEY_COLOR_GLOW)
            || key.equals(KEY_COLOR_BACKGROUND))
        {
          useCustomColors = true; // touch triggers custom colors to be used.
          SharedPreferences.Editor ed = p.edit();
          ed.putBoolean(KEY_USE_CUSTOM_COLORS, useCustomColors);
          ed.apply();
          
          applyColorOverrides();
        } /*else if (key.equals(KEY_NUM_BLOBS)) {
          int num = p.getInt(KEY_NUM_BLOBS, numBlobs);
          if (num <= BlobUtils.MAX_N_BLOBS) {
            String sNum = p.getString(KEY_NUM_BLOBS, Integer.toString(numBlobs));
            numBlobs = Integer.valueOf(sNum).intValue();
          }
        }*/
      }
    }
    protected BlobSettings settings = new BlobSettings();

    class BlobUtils {
      static final int COLORID_CENTER = 0;
      static final int COLORID_GLOW = 1;
      static final int COLORID_BACKGROUND = 2;
      static final int MAX_N_BLOBS = 200;
      static final short MAX_PRIMARY = 255; // max for r, g, or b
      static final short MID_PRIMARY = 128; // used for falloff calculations ?
      static final short  CEILING = 4095; // max pixel value in buffers? no based on sine table
      static final int TABLE_SIZE = 4096;
      //static final int RENDER_X = 128;
      //static final int RENDER_Y = 128;
      static final int RENDER_X = 64 * SCALEFACTOR;
      static final int RENDER_Y = 64 * SCALEFACTOR;
      //static final int COLORMAP_SIZE = 64;
      //static final int COLORMAP_SIZE = RENDER_X / 2;
      static final int COLORMAP_SIZE = 256;
      /*static final int RENDER_X = 128;
      static final int RENDER_Y = 128; // testing*/
      //static final int MAXBLOB_SPAN = 32; // span
      static final int MAXBLOB_SPAN = RENDER_X / 2; // span
      /*static final int MAXBLOB_SPAN = 64;*/
      static final int BLOB_RADIUS = MAXBLOB_SPAN / 2;
      static final int MAXRADIUS = (MAXBLOB_SPAN / 2) - 1; // index?. 15
      static final int RENDER_SIZE = RENDER_X * RENDER_Y;
      static final int RENDER_ORIGIN = 0;
      static final int MOVEMENT_ADJUST_Y = 256; // seems to give more vertical movement.
      //static final int MIDFACTOR_X = 2048;
      //static final int MIDFACTOR_Y = 2048;
      static final int MIDFACTOR_X = 8192;
      static final int MIDFACTOR_Y = 8192;

      protected Blob[] blobs = new Blob[MAX_N_BLOBS]; // TODO: better container

      short[] sine_table = new short[TABLE_SIZE];
      int[] myColorMap = new int[COLORMAP_SIZE];
      short[] blob_buf = new short[MAXBLOB_SPAN * MAXBLOB_SPAN];
      short[] buf = new short[RENDER_SIZE];  // indices to colormap
      int[] pixels = new int[RENDER_SIZE]; // colors mapped, ready to draw
      short[] back = new short[RENDER_SIZE];
      Matrix mtx = new Matrix();
      private BlobSettings mSettings;
      private SurfaceHolder sh;
      int frameClock = 0;
      //long lastTime = System.currentTimeMillis();

      // Android drawing stuff we don't want to keep reinstantiating w/ every frame.
      Bitmap bmp;

      Random mRandom = new Random();

      BlobUtils(BlobSettings s, SurfaceHolder h) {
        mSettings = s;
        sh = h;
        buildSineTable();
        buildBlob();
        initSpeed();
        // TODO: buildBackground(). Need to solve size problem.
        buildBackground();
        buildColorMap();

        bmp = Bitmap.createBitmap(RENDER_X, RENDER_Y, Bitmap.Config.ARGB_8888);
      }

      public void reInit() {
        buildSineTable();
        buildBlob();
        initSpeed();
        buildBackground();
        buildColorMap();
      }

      private int maxRadiusSq = MAXRADIUS * MAXRADIUS; // TODO: rename?. It's outer radius I think.
      private int maximum; // ??? 
      private int radius2; // diameter?
      private int center;

      // really not proud of this. Missed a place where a scaling factor should have been applied in
      // setParams(). Found it as I was getting ready to release and tweaking the themes. Borders
      // look much better with this scaling factor, but blob falloff is affected negatively. So
      // we'll use a separate getValue() function for the border rendering.
      private int centerBorder;

      protected void setParams(int size, int falloff, int presence) {
        maximum = CEILING * presence / MAX_PRIMARY;
        radius2 = size; 

        // map falloff to center 
        // wtf is 128 and 255 here? TODO: figure out these values
        if (falloff < MID_PRIMARY) {
          center = radius2 - 1 - (falloff * falloff * 800 / 16129);
          centerBorder = radius2 - 1 - (falloff * falloff * SCALEFACTOR * 800 / 16129);
        } else {
          falloff = MAX_PRIMARY - falloff;
          center = maxRadiusSq + 1 + (falloff * falloff * 800 / 16129);
          centerBorder = maxRadiusSq + 1 + (falloff * falloff * SCALEFACTOR * 800 / 16129);
        }
      }

      protected int getValue(int x2) {
        int i;
        if (x2 <= radius2)
          return maximum;
        else if (x2 >= maxRadiusSq)
          return 0;
        else {
          i = maximum * (center - radius2) * (x2 - maxRadiusSq) /
            ((center - x2) * (radius2 - maxRadiusSq));
          return (i <=0 ? 0 : i);
        }
      }
      protected int getValueBorder(int x2) {
        int i;
        if (x2 <= radius2)
          return maximum;
        else if (x2 >= maxRadiusSq)
          return 0;
        else {
          i = maximum * (centerBorder - radius2) * (x2 - maxRadiusSq) /
            ((centerBorder - x2) * (radius2 - maxRadiusSq));
          return (i <=0 ? 0 : i);
        }
      }

      void buildSineTable() {
        // probably won't need to increase sine table size w/ resolution. Small
        // tables work reasonably well on my DSP projects.
        int i;
        Double d = Double.valueOf(0.0);
        for (i = 0; i < TABLE_SIZE; i++) {
          d =  Math.sin( (float)i * Math.PI * 2.0 / 4096.0) * (MAXBLOB_SPAN*MAXBLOB_SPAN) + 0.5;
          sine_table[i] = d.shortValue();
        }
      }

      void buildBackground() { 
        // Should probably reference colormap; check C source implementation.

        int c, x, y;
        int index = 0;
        int halfX = RENDER_X / 2;
        int halfY = RENDER_Y / 2;

        setParams(settings.border_size,
            settings.border_falloff,
            settings.border_presence);
        for (int i = 0; i < RENDER_X; i++) {  // make sure it's Y and not X here
          for (int j = 0; j < RENDER_Y; j++) {
            if (i < halfX)
              x = i;
            else
              x = RENDER_X-1 - i;

            if (j < halfY)
              y = j;
            else
              y = RENDER_Y-1 - j;

            if (x < y)
              c = x;
            else
              c = y;

            // clip edges
             if (Math.abs(x-y) < 4) // wbk corner case? 
               c -= (4 - Math.abs(x - y)) / 2;

             back[index++] = (short)getValueBorder(c * c);
          }
        }
      }

      void buildBlob() {
        int x, y;
        int blobIndex = 0;
        setParams(mSettings.blob_size, mSettings.blob_falloff, mSettings.blob_presence);

        int min = (MAXRADIUS+1) * -1;
        int max = (MAXRADIUS+1);
        for (y = min; y < max; y++)
          for (x = min; x < max; x++)
            blob_buf[blobIndex++] = (short)getValue(x * x + y * y);
      }

      void initSpeed() {
        int i;
        final int SPEED_THRESHOLD_X = RENDER_X * 20 + RENDER_X + BLOB_RADIUS;
        final int SPEED_THRESHOLD_Y = RENDER_Y * 20 + RENDER_Y + BLOB_RADIUS;
        // TODO: gravity setting here.
        if (settings.gravity) {
          for (i = 0; i < MAX_N_BLOBS; i++) {
            blobs[i] = new Blob();
            blobs[i].vx = 0;
            blobs[i].vy = 0;
            blobs[i].x = MIDFACTOR_X;
            blobs[i].y = (int)(MIDFACTOR_Y * 1.90);  //* 2; // ROUGH approximation; original 
                                //was hardcoded 1000
          }
        } else {
          Random r = mRandom;
          for (i = 0; i < MAX_N_BLOBS; i++) {
            //d = andom(); // ARRRGH! TODO
            blobs[i] = new Blob();
            blobs[i].vx = r.nextInt() % SPEED_THRESHOLD_X;

            //d = Math.random();
            blobs[i].vy = r.nextInt() % SPEED_THRESHOLD_Y;
            /*int middle = 512;*/
            blobs[i].x = MIDFACTOR_X; // TODO: calc independently
            blobs[i].y = MIDFACTOR_Y;
          }  
        }
      }

      int interpolate(int i, int max, int rangeMin, int rangeMax) {
        int r, g, b;
        r = (Color.red(rangeMin) * (max - i) + Color.red(rangeMax) * i + max / 2) / max;
        g = (Color.green(rangeMin) * (max - i) + Color.green(rangeMax) * i + max / 2) / max;
        b = (Color.blue(rangeMin) * (max - i) + Color.blue(rangeMax) * i + max / 2) / max;
        return Color.rgb(r, g, b);
      }
      // analog to wmblob's alloc_colors(). Don't need to worry about X11 color map allocation.
      void buildColorMap() {
        // This part's iffy. Might increase colormap size. Shouldn't need to be dependent on 
        // screen dimenensions tho, unlike some other data in this class.
        int i;
        int halfmap = COLORMAP_SIZE / 2;
        int colorToAdd;
        for (i = 0; i < COLORMAP_SIZE; i++) {
          if (i <= halfmap) {
            colorToAdd = interpolate(i, halfmap, 
                        mSettings.colorBackground, mSettings.colorGlow);
          } else {
            colorToAdd = interpolate(i - halfmap, halfmap-1, 
                        mSettings.colorGlow, mSettings.colorCenter);
          }
          myColorMap[i] = colorToAdd | 0xFF000000; // Add alpha channel
        }
      }

      int sineLookup(int a) {
        return sine_table[a & 0xFFF]; // wrap table?
      }
      
      void moveBlobs() {
        // from move() in original C implementation.
        int i = 0;

        if (mSettings.gravity) {
          // TODO:
        } else { // normal, no gravity
          // TODO: frameClock * scaleFactor so I can drop framerate
          //int scaledClock = frameClock * SCALEFACTOR / 3;
          int scaledClock = frameClock;// * SCALEFACTOR / 3;
          for (i = 0; i < mSettings.numBlobs; i++) {
            blobs[i].x = 
              sineLookup(scaledClock * blobs[i].vx / BLOB_RADIUS) / 2 + MIDFACTOR_X; // TODO vars
            blobs[i].y = 
              sineLookup(scaledClock * blobs[i].vy / BLOB_RADIUS + MOVEMENT_ADJUST_Y) 
                    / 2 + MIDFACTOR_Y;
            //blobs[i].y = sineLookup(frameClock * blobs[i].vy / 16) / 2 + 512; // experimenting
          }
        }
      }
      
      void drawBlobs() {
        // clear background, otherwise colors are added in render buf.
        // TODO: build background properly.
        int i;
        for (i = 0; i < RENDER_SIZE; i++)
          buf[i] = back[i];
        
        // number here is width or height divided by blob max?
        for (i = 0; i < mSettings.numBlobs; i++)
          putBlob(blobs[i].x / BLOB_RADIUS, blobs[i].y / BLOB_RADIUS); // TODO: was 16. BLOB_RADIUS?

        frameClock++;
      }

      void putBlob(int px, int py) {
        int blobOffset = 0, bufOffset;
        int sx, sy, x;
        int blob_jump, buf_jump;
        int c;

        if (px <= (RENDER_ORIGIN - BLOB_RADIUS) || py <= (RENDER_ORIGIN - BLOB_RADIUS)
            || px >= RENDER_X + BLOB_RADIUS || py >= RENDER_Y + BLOB_RADIUS)
          return;

        bufOffset = (px - BLOB_RADIUS) + (py - BLOB_RADIUS) * RENDER_Y;

        // the next block determines how much of blob to copy, leaving out edge
        // if we're at the edge of our render grid.
        if (px < BLOB_RADIUS) { // px is parameter X and sx is side X? start X?
          sx = BLOB_RADIUS + px;
          blobOffset += BLOB_RADIUS - px;
           bufOffset += BLOB_RADIUS - px;
        } else if (px > (RENDER_X - BLOB_RADIUS)) {
          sx = (RENDER_X + BLOB_RADIUS) - px;
        } else {
          sx = MAXBLOB_SPAN;
        }

        blob_jump = MAXBLOB_SPAN - sx; // was 32. 
        buf_jump = RENDER_X - sx; // was 64. probably RENDER_X

        if (py < BLOB_RADIUS) {
          sy = BLOB_RADIUS + py;
          blobOffset += (BLOB_RADIUS - py) * MAXBLOB_SPAN;
          bufOffset += (BLOB_RADIUS - py) * RENDER_X;
        } else if (py > (RENDER_Y - BLOB_RADIUS)) { // was 48
          sy = (RENDER_Y + BLOB_RADIUS) - py; // was 80 - py
        } else {
          sy = MAXBLOB_SPAN; // was 32
        } 

        while (sy-- > 0) {
          x = sx;

          while (x-- > 0) {
            c = buf[bufOffset] + blob_buf[blobOffset] +
              buf[bufOffset] * blob_buf[blobOffset] *
              mSettings.multiplication / CEILING;  
            if (c < 0)
              c = 0;
            buf[bufOffset] = (short)((c < CEILING) ? c : CEILING);
            bufOffset++;
            blobOffset++;
          }
          bufOffset += buf_jump;
          blobOffset += blob_jump;
        }
      }

      void show(Canvas canvas) {
        int i = 0; 
        float x, y;
        bmp = Bitmap.createBitmap(RENDER_X, RENDER_Y, Bitmap.Config.ARGB_8888);

        int mapIndex;
        for (i = 0; i < RENDER_SIZE; i++) {
          mapIndex = buf[i] * (COLORMAP_SIZE-1) / CEILING;
          pixels[i] = myColorMap[mapIndex]; 
        }
        bmp.setPixels(pixels, 0, RENDER_X, 0, 0, RENDER_X, RENDER_Y); 
        //
        // TODO: might be good to get these out of the render loop, but are they guarunteed not
        // to change?
        float scaleX = canvas.getWidth() / (bmp.getWidth());
        float scaleY = canvas.getHeight() / bmp.getHeight();
        scaleX = (float)canvas.getWidth() / (float)(bmp.getWidth());
        scaleY = (float)canvas.getHeight() / (float)bmp.getHeight();
        mtx.setScale(scaleX, scaleY); // can we get this out of loop? See TODO above

        canvas.drawBitmap(bmp, mtx, null);
      }

      // experimental touch toy stuff
      void changeColors(int glow, int center) {
        mSettings.colorGlow = glow;
        mSettings.colorCenter = center;
        
        buildColorMap();
      }
      void fadeColor(int fade, int id, int fadeType) {
        if (fadeType != FADE_TYPE_NONE) {
          int mask = 0xFFFFFF;
          if (fadeType == FADE_TYPE_RED)
            mask &= 0x00FFFF;
          if (fadeType == FADE_TYPE_GREEN)
            mask &= 0xFF00FF;
          if (fadeType == FADE_TYPE_BLUE)
            mask &= 0xFFFF00;

          switch (id) {
            case COLORID_CENTER:
              mSettings.colorCenter &= mask;
              mSettings.colorCenter += fade;
              break;
            case COLORID_GLOW:  
              mSettings.colorGlow &= mask;
              mSettings.colorGlow += fade;
              break;
            case COLORID_BACKGROUND:
              mSettings.colorBackground &= mask;
              mSettings.colorBackground += fade;
              break;
            default:
          }
          buildColorMap();
        }
      }
    }
    protected BlobUtils utils = new BlobUtils(settings, sh);


    // members for parameters, based on preferences.
    //private int blobCount; // TODO: merge this with BlobSettings
    private boolean visible = true;
    private int width, height;
    private int count = 0; // for testing.

    public BlobDroidEngine() {
      handler.post(drawRunner);
    }
    
    @Override
    public void onVisibilityChanged(boolean visible) {
      this.visible = visible;
      if (visible) {
        handler.post(drawRunner);
        } else {
        handler.removeCallbacks(drawRunner);
      }
    }

    @Override
      public void onSurfaceDestroyed(SurfaceHolder holder) {
      super.onSurfaceDestroyed(holder);
          this.visible = false;
        handler.removeCallbacks(drawRunner);
    }

    @Override
    public void onSurfaceChanged(SurfaceHolder holder, int format, int width, int height) {
      this.width = width;
          this.height = height;
      super.onSurfaceChanged(holder, format, width, height);
    }

    @Override
    public void onTouchEvent(MotionEvent event) {
      // trippy stuff here.
      if (settings.touchEnabledCenter 
          || settings.touchEnabledGlow 
          || settings.touchEnabledBackground) 
      { 
        Rect frame = sh.getSurfaceFrame();
        int maxX = frame.width();
        int maxY = frame.height();
        float x = event.getX();
        float y = event.getY();
        double percentX = x / maxX;
        double percentY = y / maxY;
        //int xFade, yFade;
        int fade = 0;
        int fadeType = FADE_TYPE_NONE;
        /*  _________
         * |         | hi =  -->255
         * |   hi    |
         * |         | lo = 0-->
         * |         |
         * | R  G  B | RGB = columns for respective color selection
         * |   lo    |
         * | _  _  _ | dead = inactive area, won't interfere w/ buttons
         * |  dead   |
         * |_________|
         *
         */  
        // calculate fade magnitude based on Y position. In Android, Y starts
        // at upper left. Flip and only cover top two thirds.
        //percentY = 1.0 - percentY; // flip Y
        if (percentY <= 0.67) {
          percentY = percentY / 0.67;
          percentY = 1.0 - percentY;

          // ensure pure black and white are achievable in case of fluke of geometry
          if (percentY <= 0.08)
            percentY = .0;
          else if (percentY >= 0.96)
            percentY = 1.0;

          fade = (int)(255 * percentY);

          // determine R, G, or B based on X position.
          if (percentX <= 0.33) {
            fade = Color.rgb(fade, 0, 0);
            fadeType = FADE_TYPE_RED;
          } else if (percentX < 0.67) {
            fade = Color.rgb(0, fade, 0);
            fadeType = FADE_TYPE_GREEN;
          } else {
            fade = Color.rgb(0, 0, fade);
            fadeType = FADE_TYPE_BLUE;
          }
        }

        if (fadeType != FADE_TYPE_NONE) {
          // persist values.
          SharedPreferences.Editor ed = settings.prefs.edit();

          // 0-padding code is ugly. Java format strings don't support padding w/ leading
          // 0's apparently like C printf() would.
          // lol encapsulation
          if (settings.touchEnabledCenter) {
            utils.fadeColor(fade, BlobUtils.COLORID_CENTER, fadeType);
            ed.putString(settings.KEY_COLOR_CENTER, 
                    //"#" + Integer.toHexString(settings.colorCenter & 0x00FFFFFF));
                    "#" + String.format("%6X", 
                          settings.colorCenter & 0x00FFFFFF).replace(' ', '0'));
          } // yech, apparently String.format doesn't support precision for ints, hence replace ' ', '0'
          if (settings.touchEnabledGlow) {
            utils.fadeColor(fade, BlobUtils.COLORID_GLOW, fadeType);
            ed.putString(settings.KEY_COLOR_GLOW, 
                    //"#" + Integer.toHexString(settings.colorGlow & 0x00FFFFFF));
                    "#" + String.format("%6X", 
                        settings.colorGlow & 0x00FFFFFF).replace(' ', '0'));
          }
          if (settings.touchEnabledBackground) {
            utils.fadeColor(fade, BlobUtils.COLORID_BACKGROUND, fadeType);
            ed.putString(settings.KEY_COLOR_BACKGROUND, 
                    //"#" + Integer.toHexString(settings.colorBackground & 0x00FFFFFF));
                    "#" + String.format("%6X", 
                        settings.colorBackground & 0x00FFFFFF).replace(' ', '0'));
          }
          ed.apply();
        }

        super.onTouchEvent(event);
      }
    }

    private void draw() {
      // should be able to loop in UI thread by essentially posting this from itself.
      // Kind of like recursion, but no stack growth. I guess removeCallbacks() prevents
      // a backlog from forming?
      Canvas canvas = null;

      try {
        canvas = sh.lockCanvas();
        if (canvas != null) {
          //canvas.drawColor(Color.BLACK + count++);
          
          try {
            utils.moveBlobs();
            utils.drawBlobs();
            utils.show(canvas);
          } catch (ArrayIndexOutOfBoundsException e){}
        }
      } finally {
        if (canvas != null) {
          sh.unlockCanvasAndPost(canvas);
        }
      }
      handler.removeCallbacks(drawRunner);
      if (visible) {
        //handler.postDelayed(drawRunner, 1000 / 34); // TODO: timing
        handler.postDelayed(drawRunner, 1000 / FRAMERATE); // TODO: timing
      }
    }
  }
}




Java Source Code List

org.neon.coydog.blobdroid.BlobDroidPreferencesActivity.java
org.neon.coydog.blobdroid.BlobDroidService.java
org.neon.coydog.blobdroid.SetWallpaperActivity.java