package com.android.launcher; import java.util.Arrays; import java.util.LinkedList; import java.util.List; import android.content.res.XmlResourceParser; import android.graphics.Bitmap; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.util.DisplayMetrics; /** * Provides methods and constructs for handling themes with an icon shader. * Create parser from theme/res/xml/shader.xml * Compile with CompiledIconShader parseXml(XmlResourceParser xpp) * Process icons with Drawable processIcon(Drawable icon, CompiledIconShader c) */ class IconShader { static class IMAGE { static final int ICON = 0; static final int BUFFER = 1; static final int OUTPUT = 2; } static class MODE { static final int NONE = 0; static final int WRITE = 1; static final int MULTIPLY = 2; static final int DIVIDE = 3; static final int ADD = 4; static final int SUBTRACT = 5; } static class INPUT { static final int AVERAGE = 0; static final int INTENSITY = 1; static final int CHANNEL = 2; static final int VALUE = 3; } static class CHANNEL { static final int ALPHA = 0; static final int RED = 1; static final int GREEN = 2; static final int BLUE = 3; } static class Shader { final int mode, target, targetChannel; final int input, inputMode, inputChannel; final float inputValue; Shader(int mode, int target, int targetChannel, int input, int inputMode, int inputChannel, float inputValue) { this.mode = mode; this.target = target; this.targetChannel = targetChannel; this.input = input; this.inputMode = inputMode; this.inputChannel = inputChannel; this.inputValue = inputValue; } } static class ShaderUses { final boolean buffer, icon_intensity, buffer_intensity, output_intensity; ShaderUses(List<Shader> shaders){ boolean buffer = false, icon_intensity = false, buffer_intensity = false, output_intensity = false; for(Shader s : shaders) { if (s.mode == MODE.NONE) continue; if (s.input == IMAGE.BUFFER || s.target == IMAGE.BUFFER) buffer = true; if (s.inputMode == INPUT.INTENSITY) switch(s.input) { case IMAGE.ICON: icon_intensity = true; break; case IMAGE.BUFFER: buffer_intensity = true; break; case IMAGE.OUTPUT: output_intensity = true; break; } } this.buffer = buffer; this.icon_intensity = icon_intensity; this.buffer_intensity = buffer_intensity; this.output_intensity = output_intensity; } } static class CompiledIconShader { static final int MAXLENGTH = 5184; // 72*72 final List<Shader> shaders; final ShaderUses uses; // array references held here so that they are only allocated once final int[] pixels; final float[][] icon, buffer, output; final float[] icon_intensity, buffer_intensity, output_intensity; CompiledIconShader(List<Shader> s) { shaders = s; uses = new ShaderUses(s); pixels = new int[MAXLENGTH]; icon = new float[4][MAXLENGTH]; output = new float[4][MAXLENGTH]; if (uses.buffer) buffer = new float[4][MAXLENGTH]; else buffer = null; if (uses.icon_intensity) icon_intensity = new float[MAXLENGTH]; else icon_intensity = null; if (uses.buffer_intensity) buffer_intensity = new float[MAXLENGTH]; else buffer_intensity = null; if (uses.output_intensity) output_intensity = new float[MAXLENGTH]; else output_intensity = null; } } static CompiledIconShader parseXml(XmlResourceParser xpp) { List<Shader> shaders = new LinkedList<Shader>(); Shader s; String a0, a1, a2; try { int eventType = xpp.getEventType(); while (eventType != XmlResourceParser.END_DOCUMENT) { if (eventType == XmlResourceParser.START_TAG && xpp.getName().compareTo("exec") == 0 && xpp.getAttributeCount() == 3) { a0 = xpp.getAttributeValue(0); a1 = xpp.getAttributeValue(1); a2 = xpp.getAttributeValue(2); s = createShader(a0, a1, a2); if (s != null) shaders.add(s); } eventType = xpp.next(); } } catch (Exception e) { return null; } return new CompiledIconShader(shaders); } private static Shader createShader(String targetStr, String modeStr, String inputStr) { int mode = MODE.NONE; int target = IMAGE.OUTPUT; int targetChannel = CHANNEL.ALPHA; int input = IMAGE.ICON; int inputMode = INPUT.CHANNEL; int inputChannel = CHANNEL.ALPHA; float inputValue = 0; try { switch (modeStr.charAt(0)) { case 'W': mode = MODE.WRITE; break; case 'M': mode = MODE.MULTIPLY; break; case 'D': mode = MODE.DIVIDE; break; case 'A': mode = MODE.ADD; break; case 'S': mode = MODE.SUBTRACT; break; default: throw (new Exception()); } switch (targetStr.charAt(0)) { case 'B': target = IMAGE.BUFFER; break; case 'O': target = IMAGE.OUTPUT; break; default: throw (new Exception()); } switch (targetStr.charAt(1)) { case 'A': targetChannel = CHANNEL.ALPHA; break; case 'R': targetChannel = CHANNEL.RED; break; case 'G': targetChannel = CHANNEL.GREEN; break; case 'B': targetChannel = CHANNEL.BLUE; break; default: throw (new Exception()); } boolean isValue = false; switch (inputStr.charAt(0)) { case 'I': input = IMAGE.ICON; break; case 'B': input = IMAGE.BUFFER; break; case 'O': input = IMAGE.OUTPUT; break; default: inputValue = Float.parseFloat(inputStr); isValue = true; inputMode = INPUT.VALUE; ; } if (!isValue) switch (inputStr.charAt(1)) { case 'A': inputChannel = CHANNEL.ALPHA; break; case 'R': inputChannel = CHANNEL.RED; break; case 'G': inputChannel = CHANNEL.GREEN; break; case 'B': inputChannel = CHANNEL.BLUE; break; case 'I': inputMode = INPUT.INTENSITY; break; case 'H': inputMode = INPUT.AVERAGE; break; default: throw (new Exception()); } } catch (Exception e) { } return new Shader(mode, target, targetChannel, input, inputMode, inputChannel, inputValue); } static Drawable processIcon(Drawable icon_d, CompiledIconShader compiledShader) { List<Shader> shaders = compiledShader.shaders; Bitmap icon_bitmap=null; // get bitmap if (icon_d instanceof BitmapDrawable) { BitmapDrawable icon_bd = (BitmapDrawable) icon_d; icon_bitmap = icon_bd.getBitmap(); } else if(icon_d instanceof FastBitmapDrawable) { FastBitmapDrawable icon_bd = (FastBitmapDrawable) icon_d; icon_bitmap = icon_bd.getBitmap(); } else return null; if (icon_bitmap == null) return null; int width = icon_bitmap.getWidth(); int height = icon_bitmap.getHeight(); int length = width * height; if (length > CompiledIconShader.MAXLENGTH) return null; int[] pixels = compiledShader.pixels; float[][] icon = compiledShader.icon; float[][] buffer = compiledShader.buffer; float[][] output = compiledShader.output; float icon_average = 0; float buffer_average = 0; float output_average = 0; boolean icon_average_valid = false; boolean buffer_average_valid = false; boolean output_average_valid = false; float[] icon_intensity = compiledShader.icon_intensity; float[] buffer_intensity = compiledShader.buffer_intensity;; float[] output_intensity = compiledShader.output_intensity; boolean icon_intensity_valid = false; boolean buffer_intensity_valid = false; boolean output_intensity_valid = false; // convert to float icon_bitmap.getPixels(pixels, 0, width, 0, 0, width, height); for (int i = 0; i < length; i++) { icon[CHANNEL.BLUE][i] = pixels[i] & 0x000000FF; icon[CHANNEL.GREEN][i] = (pixels[i] >> 8) & 0x000000FF; icon[CHANNEL.RED][i] = (pixels[i] >> 16) & 0x000000FF; icon[CHANNEL.ALPHA][i] = (pixels[i] >> 24) & 0x000000FF; } // temporary pointers/values float inputValue = 0; float[] inputArray = null; float[] targetArray = null; // process each shader for (Shader s : shaders) { if (s.mode == MODE.NONE) continue; // determine input if (s.inputMode == INPUT.AVERAGE) { switch (s.input) { case IMAGE.ICON: if (!icon_average_valid) { icon_average = getAverage(icon, length); icon_average_valid = true; } inputValue = icon_average; break; case IMAGE.BUFFER: if (!buffer_average_valid) { buffer_average = getAverage(buffer, length); buffer_average_valid = true; } inputValue = buffer_average; break; case IMAGE.OUTPUT: if (!output_average_valid) { output_average = getAverage(output, length); output_average_valid = true; } inputValue = output_average; break; } } if (s.inputMode == INPUT.INTENSITY) { switch (s.input) { case IMAGE.ICON: if (!icon_intensity_valid) { getIntensity(icon_intensity, icon, length); icon_intensity_valid = true; } inputArray = icon_intensity; break; case IMAGE.BUFFER: if (!buffer_intensity_valid) { getIntensity(buffer_intensity, buffer, length); buffer_intensity_valid = true; } inputArray = buffer_intensity; break; case IMAGE.OUTPUT: if (!output_intensity_valid) { getIntensity(output_intensity, output, length); output_intensity_valid = true; } inputArray = output_intensity; break; } } if (s.inputMode == INPUT.CHANNEL) { switch (s.input) { case IMAGE.ICON: inputArray = icon[s.inputChannel]; break; case IMAGE.BUFFER: inputArray = buffer[s.inputChannel]; break; case IMAGE.OUTPUT: inputArray = output[s.inputChannel]; break; } } if (s.inputMode == INPUT.VALUE) { inputValue = s.inputValue; } // determine target if (s.target == IMAGE.BUFFER) { targetArray = buffer[s.targetChannel]; } if (s.target == IMAGE.OUTPUT) { targetArray = output[s.targetChannel]; //main.text += "target output\n"; } // write to target switch (s.mode) { case MODE.WRITE: if (s.inputMode == INPUT.AVERAGE || s.inputMode == INPUT.VALUE) { Arrays.fill(targetArray, inputValue); } if (s.inputMode == INPUT.INTENSITY || s.inputMode == INPUT.CHANNEL) { System.arraycopy(inputArray, 0, targetArray, 0, length); } break; case MODE.MULTIPLY: if (s.inputMode == INPUT.AVERAGE || s.inputMode == INPUT.VALUE) { for (int i = 0; i < length; i++) targetArray[i] *= inputValue; } if (s.inputMode == INPUT.INTENSITY || s.inputMode == INPUT.CHANNEL) { for (int i = 0; i < length; i++) targetArray[i] *= inputArray[i]; } break; case MODE.DIVIDE: if (s.inputMode == INPUT.AVERAGE || s.inputMode == INPUT.VALUE) { // multiply by 1/value inputValue = 1 / inputValue; for (int i = 0; i < length; i++) targetArray[i] *= inputValue; } if (s.inputMode == INPUT.INTENSITY || s.inputMode == INPUT.CHANNEL) { for (int i = 0; i < length; i++) targetArray[i] /= inputArray[i]; } break; case MODE.ADD: if (s.inputMode == INPUT.AVERAGE || s.inputMode == INPUT.VALUE) { for (int i = 0; i < length; i++) targetArray[i] += inputValue; } if (s.inputMode == INPUT.INTENSITY || s.inputMode == INPUT.CHANNEL) { for (int i = 0; i < length; i++) targetArray[i] += inputArray[i]; } break; case MODE.SUBTRACT: if (s.inputMode == INPUT.AVERAGE || s.inputMode == INPUT.VALUE) { for (int i = 0; i < length; i++) targetArray[i] -= inputValue; } if (s.inputMode == INPUT.INTENSITY || s.inputMode == INPUT.CHANNEL) { for (int i = 0; i < length; i++) targetArray[i] -= inputArray[i]; } break; } // invalidate average/intensity switch (s.target) { case IMAGE.BUFFER: buffer_average_valid = false;; buffer_intensity_valid = false; break; case IMAGE.OUTPUT: output_average_valid = false; output_intensity_valid = false; break; } } // finished processing // convert back to 32bit color int a, r, g, b; for (int i = 0; i < length; i++) { a = (int) output[CHANNEL.ALPHA][i]; r = (int) output[CHANNEL.RED][i]; g = (int) output[CHANNEL.GREEN][i]; b = (int) output[CHANNEL.BLUE][i]; a = a > 255 ? 255 : a < 0 ? 0 : a; r = r > 255 ? 255 : r < 0 ? 0 : r; g = g > 255 ? 255 : g < 0 ? 0 : g; b = b > 255 ? 255 : b < 0 ? 0 : b; a <<= 8; a |= r; a <<= 8; a |= g; a <<= 8; a |= b; pixels[i] = a; } // build drawable Bitmap.Config c = (icon_bitmap.getConfig()==null) ? Bitmap.Config.ARGB_8888 : icon_bitmap.getConfig(); Bitmap output_bitmap = Bitmap.createBitmap(pixels, width, height, c); output_bitmap.setDensity(DisplayMetrics.DENSITY_DEFAULT); BitmapDrawable output_bd = new BitmapDrawable(output_bitmap); return output_bd; } private static float getAverage(float[][] array, int length) { double average = 0; double total = 0; for (int i = 0; i < length; i++) { average += array[0][i] * (array[1][i] + array[2][i] + array[3][i]) / 3; total += array[0][i]; } average /= total; return (float) average; } private static void getIntensity(float[] intensity, float[][] array, int length) { for (int i = 0; i < length; i++) intensity[i] = (array[1][i] + array[2][i] + array[3][i]) / 3; } }