From 6e7d9b7cd83a83265137090df3aa543b49d1574f Mon Sep 17 00:00:00 2001 From: Vadim Lopatin Date: Fri, 11 Apr 2014 17:14:39 +0400 Subject: [PATCH] support color transform in state drawables --- examples/example1/res/btn_default_small.xml | 3 +- .../res/btn_default_small_transparent.xml | 7 +- src/dlangui/graphics/drawbuf.d | 49 +++++- src/dlangui/graphics/resources.d | 148 +++++++++++++++++- src/dlangui/widgets/tabs.d | 1 + 5 files changed, 196 insertions(+), 12 deletions(-) diff --git a/examples/example1/res/btn_default_small.xml b/examples/example1/res/btn_default_small.xml index 0ad26807..358969f7 100644 --- a/examples/example1/res/btn_default_small.xml +++ b/examples/example1/res/btn_default_small.xml @@ -20,5 +20,6 @@ android:drawable="btn_default_small_normal_hover" android:state_hovered="true" /> + android:drawable="btn_default_small_normal" + /> diff --git a/examples/example1/res/btn_default_small_transparent.xml b/examples/example1/res/btn_default_small_transparent.xml index 2f38acbe..30b2f89e 100644 --- a/examples/example1/res/btn_default_small_transparent.xml +++ b/examples/example1/res/btn_default_small_transparent.xml @@ -5,19 +5,24 @@ android:variablePadding="false" > + color_transform_add1="100,0,0,0" + android:state_enabled="false" android:state_focused="true" /> diff --git a/src/dlangui/graphics/drawbuf.d b/src/dlangui/graphics/drawbuf.d index 0d5a0718..9bca7733 100644 --- a/src/dlangui/graphics/drawbuf.d +++ b/src/dlangui/graphics/drawbuf.d @@ -4,7 +4,7 @@ public import dlangui.core.types; import dlangui.core.logger; immutable uint COLOR_TRANSFORM_OFFSET_NONE = 0x80808080; -immutable uint COLOR_TRANSFORM_MULTIPLY_NONE = 0x80808080; +immutable uint COLOR_TRANSFORM_MULTIPLY_NONE = 0x40404040; /// blend two RGB pixels using alpha uint blendARGB(uint dst, uint src, uint alpha) { @@ -31,6 +31,16 @@ ubyte rgbToGray(uint color) { return cast(uint)(((srcr + srcg + srcg + srcb) >> 2) & 0xFF); } +// todo +struct ColorTransformHandler { + void init(ref ColorTransform transform) { + + } + uint transform(uint color) { + return color; + } +} + uint transformComponent(int src, int addBefore, int multiply, int addAfter) { int add1 = (cast(int)(addBefore << 1)) - 0x100; int add2 = (cast(int)(addAfter << 1)) - 0x100; @@ -51,6 +61,21 @@ uint transformRGBA(uint src, uint addBefore, uint multiply, uint addAfter) { return (a << 24) | (r << 16) | (g << 8) | b; } +struct ColorTransform { + uint addBefore = COLOR_TRANSFORM_OFFSET_NONE; + uint multiply = COLOR_TRANSFORM_MULTIPLY_NONE; + uint addAfter = COLOR_TRANSFORM_OFFSET_NONE; + @property bool empty() const { + return addBefore == COLOR_TRANSFORM_OFFSET_NONE + && multiply == COLOR_TRANSFORM_MULTIPLY_NONE + && addAfter == COLOR_TRANSFORM_OFFSET_NONE; + } + uint transform(uint color) { + return transformRGBA(color, addBefore, multiply, addAfter); + } +} + + /// blend two RGB pixels using alpha ubyte blendGray(ubyte dst, ubyte src, uint alpha) { uint ialpha = 256 - alpha; @@ -262,7 +287,7 @@ class DrawBuf : RefCountedObject { } /// create drawbuf with copy of current buffer with changed colors (returns this if not supported) - DrawBuf transformColors(uint addBefore, uint multiply, uint addAfter) { + DrawBuf transformColors(ref ColorTransform transform) { return this; } @@ -706,15 +731,27 @@ class ColorDrawBuf : ColorDrawBufBase { for (int i = 0; i < len; i++) p[i] = color; } - override DrawBuf transformColors(uint addBefore, uint multiply, uint addAfter) { - if (addBefore == COLOR_TRANSFORM_OFFSET_NONE && addAfter == COLOR_TRANSFORM_OFFSET_NONE && multiply == COLOR_TRANSFORM_MULTIPLY_NONE) + override DrawBuf transformColors(ref ColorTransform transform) { + if (transform.empty) return this; + bool skipFrame = hasNinePatch; ColorDrawBuf res = new ColorDrawBuf(_dx, _dy); + if (hasNinePatch) { + NinePatch * p = new NinePatch; + *p = *_ninePatch; + res.ninePatch = p; + } for (int y = 0; y < _dy; y++) { uint * srcline = scanLine(y); uint * dstline = res.scanLine(y); - for (int x = 0; x < _dx; x++) - dstline[x] = transformRGBA(srcline[x], addBefore, multiply, addAfter); + bool allowTransformY = !skipFrame || (y !=0 && y != _dy - 1); + for (int x = 0; x < _dx; x++) { + bool allowTransformX = !skipFrame || (x !=0 && x != _dx - 1); + if (!allowTransformX || !allowTransformY) + dstline[x] = srcline[x]; + else + dstline[x] = transform.transform(srcline[x]); + } } return res; } diff --git a/src/dlangui/graphics/resources.d b/src/dlangui/graphics/resources.d index 1697e5f3..ddd13b75 100644 --- a/src/dlangui/graphics/resources.d +++ b/src/dlangui/graphics/resources.d @@ -7,6 +7,7 @@ import std.file; import std.algorithm; import std.xml; import std.algorithm; +import std.conv; class Drawable : RefCountedObject { @@ -230,6 +231,7 @@ class StateDrawable : Drawable { static struct StateItem { uint stateMask; uint stateValue; + ColorTransform transform; DrawableRef drawable; @property bool matchState(uint state) { return (stateMask & state) == stateValue; @@ -242,11 +244,11 @@ class StateDrawable : Drawable { // max drawable size for all states protected Point _size; - void addState(uint stateMask, uint stateValue, string resourceId) { + void addState(uint stateMask, uint stateValue, string resourceId, ref ColorTransform transform) { StateItem item; item.stateMask = stateMask; item.stateValue = stateValue; - item.drawable = drawableCache.get(resourceId); + item.drawable = drawableCache.get(resourceId, transform); itemAdded(item); } @@ -269,15 +271,69 @@ class StateDrawable : Drawable { } } + /// parse 4 comma delimited integers + static bool parseList4(T)(string value, ref T[4] items) { + int index = 0; + int p = 0; + int start = 0; + for (;p < value.length && index < 4; p++) { + while (p < value.length && value[p] != ',') + p++; + if (p > start) { + int end = p; + string s = value[start .. end]; + items[index++] = to!T(s); + start = p + 1; + } + } + return index == 4; + } + private static uint colorTransformFromStringAdd(string value) { + if (value is null) + return COLOR_TRANSFORM_OFFSET_NONE; + int n[4]; + if (!parseList4(value, n)) + return COLOR_TRANSFORM_OFFSET_NONE; + foreach (ref item; n) { + item = item / 2 + 0x80; + if (item < 0) + item = 0; + if (item > 0xFF) + item = 0xFF; + } + return (n[0] << 24) | (n[1] << 16) | (n[2] << 8) | (n[3] << 0); + } + private static uint colorTransformFromStringMult(string value) { + if (value is null) + return COLOR_TRANSFORM_MULTIPLY_NONE; + float n[4]; + uint nn[4]; + if (!parseList4!float(value, n)) + return COLOR_TRANSFORM_MULTIPLY_NONE; + for(int i = 0; i < 4; i++) { + int res = cast(int)(n[i] * 0x40); + if (res < 0) + res = 0; + if (res > 0xFF) + res = 0xFF; + nn[i] = res; + } + return (nn[0] << 24) | (nn[1] << 16) | (nn[2] << 8) | (nn[3] << 0); + } + bool load(Element element) { foreach(item; element.elements) { if (item.tag.name.equal("item")) { string drawableId = attrValue(item, "drawable", "android:drawable"); + ColorTransform transform; + transform.addBefore = colorTransformFromStringAdd(attrValue(item, "color_transform_add1", "android:transform_color_add1")); + transform.multiply = colorTransformFromStringMult(attrValue(item, "color_transform_mul", "android:transform_color_mul")); + transform.addAfter = colorTransformFromStringAdd(attrValue(item, "color_transform_add2", "android:transform_color_add2")); if (drawableId !is null) { uint stateMask, stateValue; extractStateFlags(item.tag.attr, stateMask, stateValue); if (drawableId !is null) { - addState(stateMask, stateValue, drawableId); + addState(stateMask, stateValue, drawableId, transform); } } } @@ -341,22 +397,42 @@ class ImageCache { static class ImageCacheItem { string _filename; DrawBufRef _drawbuf; + DrawBufRef[ColorTransform] _transformMap; + bool _error; // flag to avoid loading of file if it has been failed once bool _used; this(string filename) { _filename = filename; } + /// get normal image @property ref DrawBufRef get() { if (!_drawbuf.isNull || _error) { _used = true; return _drawbuf; } _drawbuf = loadImage(_filename); + if (_filename.endsWith(".9.png")) + _drawbuf.detectNinePatch(); _used = true; if (_drawbuf.isNull) _error = true; return _drawbuf; } + /// get color transformed image + @property ref DrawBufRef get(ref ColorTransform transform) { + if (transform.empty) + return get(); + if (transform in _transformMap) + return _transformMap[transform]; + DrawBufRef src = get(); + if (src.isNull) + _transformMap[transform] = src; + else { + DrawBufRef t = src.transformColors(transform); + _transformMap[transform] = t; + } + return _transformMap[transform]; + } /// remove from memory, will cause reload on next access void compact() { if (!_drawbuf.isNull) @@ -382,6 +458,17 @@ class ImageCache { ImageCacheItem item = new ImageCacheItem(filename); _map[filename] = item; return item.get; + } + /// get and cache color transformed image + ref DrawBufRef get(string filename, ref ColorTransform transform) { + if (transform.empty) + return get(filename); + if (filename in _map) { + return _map[filename].get(transform); + } + ImageCacheItem item = new ImageCacheItem(filename); + _map[filename] = item; + return item.get(transform); } // clear usage flags for all entries void checkpoint() { @@ -424,7 +511,7 @@ __gshared DrawableCache _drawableCache; @property void drawableCache(DrawableCache cache) { if (_drawableCache !is null) destroy(_drawableCache); - _drawableCache = cache; + _drawableCache = cache; } shared static this() { @@ -440,6 +527,8 @@ class DrawableCache { bool _error; bool _used; DrawableRef _drawable; + DrawableRef[ColorTransform] _transformed; + //private int _instanceCount; this(string id, string filename, bool tiled) { _id = id; @@ -494,6 +583,39 @@ class DrawableCache { } return _drawable; } + /// returns drawable (loads from file if necessary) + @property ref DrawableRef drawable(ref ColorTransform transform) { + if (transform.empty) + return drawable(); + if (transform in _transformed) + return _transformed[transform]; + _used = true; + if (!_drawable.isNull || _error) + return _drawable; + if (_filename !is null) { + // reload from file + if (_filename.endsWith(".xml")) { + // XML drawables support + StateDrawable d = new StateDrawable(); + if (!d.load(_filename)) { + destroy(d); + _error = true; + } else { + _drawable = d; + } + } else { + // PNG/JPEG drawables support + DrawBufRef image = imageCache.get(_filename, transform); + if (!image.isNull) { + bool ninePatch = _filename.endsWith(".9.png"); + _transformed[transform] = new ImageDrawable(image, _tiled, ninePatch); + return _transformed[transform]; + } else + _error = true; + } + } + return _drawable; + } } void clear() { Log.d("DrawableCache.clear()"); @@ -532,6 +654,24 @@ class DrawableCache { _idToDrawableMap[id] = item; return item.drawable; } + ref DrawableRef get(string id, ref ColorTransform transform) { + if (transform.empty) + return get(id); + if (id.equal("@null")) + return _nullDrawable; + if (id in _idToDrawableMap) + return _idToDrawableMap[id].drawable(transform); + string resourceId = id; + bool tiled = false; + if (id.endsWith(".tiled")) { + resourceId = id[0..$-6]; // remove .tiled + tiled = true; + } + string filename = findResource(resourceId); + DrawableCacheItem item = new DrawableCacheItem(id, filename, tiled); + _idToDrawableMap[id] = item; + return item.drawable(transform); + } @property string[] resourcePaths() { return _resourcePaths; } diff --git a/src/dlangui/widgets/tabs.d b/src/dlangui/widgets/tabs.d index 240ed975..9298a21a 100644 --- a/src/dlangui/widgets/tabs.d +++ b/src/dlangui/widgets/tabs.d @@ -155,6 +155,7 @@ class TabControl : WidgetGroup { _moreButton = new ImageButton("MORE", "tab_more"); _moreButton.styleId = "BUTTON_TRANSPARENT"; _moreButton.onClickListener = &onClick; + _moreButton.margins(Rect(3,3,3,6)); _enableCloseButton = true; styleId = "TAB_UP"; addChild(_moreButton); // first child is always MORE button, the rest corresponds to tab list