瀏覽代碼

Photoshop gradient files support (#200)

Added GRD Photoshop gradient support
Mathieu Capdegelle 8 年之前
父節點
當前提交
e966df17db
共有 5 個文件被更改,包括 381 次插入0 次删除
  1. 52 0
      hxd/fmt/grd/Data.hx
  2. 173 0
      hxd/fmt/grd/Reader.hx
  3. 2 0
      hxd/res/FileTree.hx
  4. 145 0
      hxd/res/Gradients.hx
  5. 9 0
      hxd/res/Loader.hx

+ 52 - 0
hxd/fmt/grd/Data.hx

@@ -0,0 +1,52 @@
+package hxd.fmt.grd;
+
+class Gradient {
+	public var name              : String;
+	public var interpolation     : Float;
+	public var colorStops        : Array<ColorStop>;
+	public var transparencyStops : Array<TransparencyStop>;
+	public var gradientStops     : Array<GradientStop>;
+
+	public function new() {
+		colorStops = [];
+		transparencyStops = [];
+		gradientStops = [];
+	}
+}
+
+class ColorStop {
+	public var color    : Color;
+	public var location : Int;
+	public var midpoint : Int;
+	public var type     : ColorStopType;
+
+	public function new() {}
+}
+
+enum ColorStopType {
+	User;
+	Background;
+	Foreground;
+}
+
+class TransparencyStop  {
+	public var opacity  : Float;
+	public var location : Int;
+	public var midpoint : Int;
+
+	public function new() {}
+}
+
+enum Color {
+	RGB(r:Float, g:Float, b:Float);
+	HSB(h:Float, s:Float, b:Float);
+}
+
+class GradientStop {
+	public var opacity   : Float;
+	public var colorStop : ColorStop;
+
+	public function new() {}
+}
+
+class Data extends haxe.ds.StringMap<Gradient> { }

+ 173 - 0
hxd/fmt/grd/Reader.hx

@@ -0,0 +1,173 @@
+package hxd.fmt.grd;
+import hxd.fmt.grd.Data;
+
+// http://www.tonton-pixel.com/Photoshop%20Additional%20File%20Formats/gradients-file-format.html
+
+class Reader {
+	var i : haxe.io.Input;
+	var version : Int;
+
+	public function new(i) {
+		this.i = i;
+		i.bigEndian = true;
+	}
+
+	function readUnicode(input : haxe.io.Input, len : Int) : String {
+		var res = "";
+		for (i in 0...len - 1) res += String.fromCharCode(input.readInt16());
+		input.readInt16();
+		return res;
+	}
+
+	function parseValue(i : haxe.io.Input) : Dynamic {
+		var type = i.readString(4);
+		var value : Dynamic;
+		switch (type) {
+			case "Objc" : value = parseObj (i);
+            case "VlLs" : value = parseList(i);
+            case "doub" : value = i.readDouble();
+            case "UntF" : i.readString(4); value = i.readDouble();
+            case "TEXT" : value = readUnicode(i, i.readInt32());
+            case "enum" : value = parseEnum(i);
+            case "long" : value = i.readInt32();
+            case "bool" : value = i.readByte();
+            case "tdtd" : var len = i.readInt32(); value = { length : len, value : i.read(len) };
+			default     : throw "Unhandled type \"" + type + "\"";
+		}
+		return value;
+	}
+
+	function parseObj(i : haxe.io.Input) : Dynamic {
+		var len  = i.readInt32(); if (len == 0) len = 4;
+		var name = readUnicode(i, len);
+
+		len = i.readInt32(); if (len == 0) len = 4;
+		var type = i.readString(len);
+
+		var obj = { name : name, type : type }
+
+		var numProperties = i.readInt32();
+		for (pi in 0...numProperties) {
+			len = i.readInt32(); if (len == 0) len = 4;
+			var key = i.readString(len);
+			var si = key.indexOf(" ");
+			if (si > 0) key = key.substring(0, si);
+			Reflect.setField(obj, key, parseValue(i));
+		}
+
+		return obj;
+	}
+
+	function parseList(i : haxe.io.Input) {
+		var res = new Array<Dynamic>();
+		var len = i.readInt32();
+		for (li in 0...len)
+			res.push(parseValue(i));
+		return res;
+	}
+
+	function parseEnum(i : haxe.io.Input) {
+		var len  = i.readInt32(); if (len == 0) len = 4;
+		var type = i.readString(len);
+		len = i.readInt32(); if (len == 0) len = 4;
+		var value = i.readString(len);
+		return { type : type, value : value };
+	}
+
+	public function read() : Data {
+		var d = new Data();
+		i.read(32);      // skip header
+		i.readString(4); // main object
+
+		var list = cast(parseValue(i), Array<Dynamic>);
+		for (obj in list) {
+			var obj = obj.Grad;
+			var grd = new Gradient();
+
+			grd.name = obj.Nm.substring(obj.Nm.indexOf("=") + 1);
+			grd.interpolation = obj.Intr;
+
+			createColorStops        (obj.Clrs, grd.colorStops);
+			createTransparencyStops (obj.Trns, grd.transparencyStops);
+			createGradientStops     (grd.colorStops, grd.transparencyStops, grd.gradientStops);
+
+			d.set(grd.name, grd);
+		}
+		return d;
+	}
+
+	function createColorStops(list : Array<Dynamic>, out : Array<ColorStop>) {
+		for (e in list) {
+			var color = Color.RGB(0, 0, 0);
+			var type  : ColorStopType;
+			switch(e.Type.value) {
+				case "UsrS" : type = User;
+				case "BckC" : type = Background;
+				case "FrgC" : type = Foreground;
+				default : throw "unhalndled color stop type : " + e.Type.value;
+			}
+
+			if (type == User) {
+				switch(e.Clr.type) {
+					case "RGBC" : color = Color.RGB(e.Clr.Rd, e.Clr.Grn,  e.Clr.Bl);
+					case "HSBC" : color = Color.HSB(e.Clr.H,  e.Clr.Strt, e.Clr.Brgh);
+					default : //throw "unhandled color type : " + e.Clr.type;
+				}
+			}
+
+			var stop = new ColorStop();
+			stop.color = color;
+			stop.location = e.Lctn;
+			stop.midpoint = e.Mdpn;
+			stop.type = type;
+			out.push(stop);
+		}
+	}
+
+	function createTransparencyStops(list : Array<Dynamic>, out : Array<TransparencyStop>) {
+		for (e in list) {
+			var stop = new TransparencyStop();
+			stop.opacity = e.Opct;
+			stop.location = e.Lctn;
+			stop.midpoint = e.Mdpn;
+			out.push(stop);
+		}
+	}
+
+	function createGradientStops(
+		clrs : Array<ColorStop>,
+		trns : Array<TransparencyStop>,
+		out : Array<GradientStop>) {
+
+		for (clr in clrs) {
+			var stop = new GradientStop();
+			stop.opacity = getOpacity(clr, trns);
+			stop.colorStop = clr;
+			out.push(stop);
+		}
+	}
+
+	function getOpacity(clr : ColorStop, trns : Array<TransparencyStop>) {
+		var index = -1;
+		for (i in 0...trns.length) {
+			var t = trns[i];
+			if (t.location >= clr.location) {
+				index = i;
+				break;
+			}
+		}
+
+		if (index == 0) return trns[0].opacity;
+		if (index <  0) return trns[trns.length - 1].opacity;
+
+		var prev = trns[index - 1];
+		var next = trns[index];
+		var w = next.location - prev.location;
+		var h = next.opacity - prev.opacity;
+
+		if (w == 0) return prev.opacity;
+		var m = h / w;
+		var b = prev.opacity - (m * prev.location);
+		return m * clr.location + b;
+	}
+}

+ 2 - 0
hxd/res/FileTree.hx

@@ -392,6 +392,8 @@ class FileTree {
 			return { e : macro loader.loadTiledMap($epath), t : macro : hxd.res.TiledMap };
 		case "atlas":
 			return { e : macro loader.loadAtlas($epath), t : macro : hxd.res.Atlas };
+		case "grd":
+			return { e : macro loader.loadGradients($epath), t : macro : hxd.res.Gradients };
 		default:
 			return { e : macro loader.loadData($epath), t : macro : hxd.res.Resource };
 		}

+ 145 - 0
hxd/res/Gradients.hx

@@ -0,0 +1,145 @@
+package hxd.res;
+import hxd.fmt.grd.Data;
+
+class Gradients extends Resource {
+	var data : Data;
+
+	// creates a texture for the specified "name" gradient
+	public function toTexture(name : String, ?resolution = 256) : h3d.mat.Texture {
+		var data = getData();
+		return createTexture([data.get(name)], resolution);
+	}
+
+	// creates a texture for each gradient
+	public function toTextureMap(?resolution = 256) : Map<String, h3d.mat.Texture> {
+		var map  = new Map<String, h3d.mat.Texture>();
+		var data = getData();
+		for (d in data) map.set(d.name, createTexture([d], resolution));
+		return map;
+	}
+
+	// all gradients are written into the same texture
+	public function toTileMap(?resolution = 256) : Map<String, h2d.Tile> {
+		var data  = getData();
+		var grads = [for (d in data) d];
+		var tex   = createTexture(grads, resolution);
+		var tile  = h2d.Tile.fromTexture(tex);
+
+		var map = new Map<String, h2d.Tile>();
+		var y = 1;
+		for (d in grads) {
+			map.set(d.name, tile.sub(0, y, resolution, 1));
+			y += 3;
+		}
+		return map;
+	}
+
+	static function createTexture(grads : Array<Gradient>, twid : Int) {
+		if (!isPOT(twid)) throw "gradient resolution should be a power of two";
+
+		var ghei = grads.length > 1 ? 3 : 1;
+		var thei = nextPOT(ghei * grads.length);
+		var tex  = new h3d.mat.Texture(twid, thei);
+
+		function uploadPixels() {
+			var pixels = hxd.Pixels.alloc(twid, thei, ARGB);
+			var yoff   = 0;
+			for (g in grads) {
+				appendPixels(pixels, g, tex.width, ghei, yoff);
+				yoff += ghei;
+			}
+			tex.uploadPixels(pixels);
+			pixels.dispose();
+		}
+
+		uploadPixels();
+		tex.realloc = uploadPixels;
+		return tex;
+	}
+
+	static inline function isPOT(v : Int) : Bool {
+		return (v & (v - 1)) == 0;
+	}
+
+	static inline function nextPOT(v : Int) : Int {
+		--v;
+		v |= v >> 1;
+		v |= v >> 2;
+		v |= v >> 4;
+		v |= v >> 8;
+		v |= v >> 16;
+		return ++v;
+	}
+
+	static function appendPixels(pixels : hxd.Pixels, dat : Gradient, wid : Int, hei : Int, yoff : Int) {
+		var colors = new Array<{value : h3d.Vector, loc : Int}>();
+
+		{	// preprocess gradient data
+			for (cs in dat.gradientStops) {
+				var color : h3d.Vector;
+				switch(cs.colorStop.color) {
+					case RGB(r, g, b): color = new h3d.Vector(r / 255, g / 255, b / 255);
+					case HSB(h, s, b): color = HSVtoRGB(h, s / 100, b / 100);
+					default : throw "unhandled color type";
+				}
+				color.w = cs.opacity / 100;
+				colors.push({value : color, loc : Std.int((wid-1) * cs.colorStop.location / dat.interpolation)});
+			}
+			colors.sort(function(a, b) { return a.loc - b.loc; } );
+
+			if (colors[0].loc > 0)
+				colors.unshift( { value : colors[0].value, loc : 0 } );
+			if (colors[colors.length - 1].loc < wid - 1)
+				colors.push( { value : colors[colors.length-1].value, loc : wid-1 } );
+		}
+
+		{	// create gradient pixels
+			var px = 0;
+			var ci = 0; // color index
+			var tmpCol = new h3d.Vector();
+
+			while (px < wid) {
+				var prevLoc = colors[ci    ].loc;
+				var nextLoc = colors[ci + 1].loc;
+
+				var prevCol = colors[ci    ].value;
+				var nextCol = colors[ci + 1].value;
+
+				while (px <= nextLoc) {
+					tmpCol.lerp(prevCol, nextCol, (px - prevLoc) / (nextLoc - prevLoc));
+					for (py in 0...hei) pixels.setPixel(px, yoff + py, tmpCol.toColor());
+					++px;
+				}
+				++ci;
+			}
+		}
+	}
+
+	static function HSVtoRGB(h : Float, s : Float, v : Float) : h3d.Vector
+	{
+		var i : Int;
+		var f : Float; var p : Float; var q : Float; var t : Float;
+		if( s == 0 )
+			return new h3d.Vector(v, v, v);
+		h /= 60;
+		i = Math.floor( h );
+		f = h - i;
+		p = v * ( 1 - s );
+		q = v * ( 1 - s * f );
+		t = v * ( 1 - s * ( 1 - f ) );
+		switch( i ) {
+			case 0 : return new h3d.Vector(v, t, p);
+			case 1 : return new h3d.Vector(q, v, p);
+			case 2 : return new h3d.Vector(p, v, t);
+			case 3 : return new h3d.Vector(p, q, v);
+			case 4 : return new h3d.Vector(t, p, v);
+			default: return new h3d.Vector(v, p, q);
+		}
+	}
+
+	function getData() : Data {
+		if (data != null) return data;
+		data = new hxd.fmt.grd.Reader(new hxd.fs.FileInput(entry)).read();
+		return data;
+	}
+}

+ 9 - 0
hxd/res/Loader.hx

@@ -81,6 +81,15 @@ class Loader {
 		return new Atlas(fs.get(path));
 	}
 
+	function loadGradients( path : String ) {
+		var g : Gradients = cache.get(path);
+		if( g == null ) {
+			g = new Gradients(fs.get(path));
+			cache.set(path, g);
+		}
+		return g;
+	}
+
 	public function dispose() {
 		cleanCache();
 		fs.dispose();