瀏覽代碼

- Added SVG to MSDF conversion - Improved AlphaMSDF shader

TothBenoit 1 年之前
父節點
當前提交
fa2e15717a
共有 2 個文件被更改,包括 412 次插入1 次删除
  1. 1 1
      hide/comp/TextureSelect.hx
  2. 411 0
      hide/view/MSDFView.hx

+ 1 - 1
hide/comp/TextureSelect.hx

@@ -8,7 +8,7 @@ class TextureSelect extends FileSelect {
 
 	public function new(?parent,?root) {
 		preview = new Element("<div class='texture-preview'>");
-		super(hide.Ide.IMG_EXTS, parent, root);
+		super(hide.Ide.IMG_EXTS.concat(["svg"]), parent, root);
 		preview.insertAfter(element);
 	}
 

+ 411 - 0
hide/view/MSDFView.hx

@@ -0,0 +1,411 @@
+package hide.view;
+
+private class MSDFViewerShader extends h3d.shader.AlphaMSDF {
+	static var SRC = {
+
+		@param var comparisonFactor : Float;
+
+		function fragment() {
+			if (calculatedUV.x > comparisonFactor)
+				pixelColor = texture.get(calculatedUV);
+			else {
+				pixelColor.rgb = vec3(1.0);					
+				var sample = texture.get(calculatedUV);
+				var sd = median(sample.r, sample.g, sample.b);
+				var screenPxDistance = screenPxRange(calculatedUV)*(sd - 0.5);
+				pixelColor.a = clamp(screenPxDistance + 0.5, 0.0, 1.0);
+			}
+		}
+	}
+}
+
+enum MSDFViewMode {
+	MSDF;
+	Raw;
+	Comparison;
+}
+
+class MSDFView extends FileView {
+
+	var bmp : h2d.Bitmap;
+	var sliderBmp : h2d.Graphics;
+	var shader : MSDFViewerShader;
+	var scene : hide.comp.Scene;
+	var viewMode : MSDFViewMode = MSDF;
+	var interactive : h2d.Interactive;
+	var tools : hide.comp.Toolbar;
+	var cam : Dynamic;
+	var currentSize : Float = 0;
+
+	override function onDisplay() {
+		cleanUp();
+
+		element.html('
+			<div class="flex vertical">
+				<div class="toolbar"></div>
+				<div class="scene-partition" style="display: flex; flex-direction: row; flex: 1; overflow: hidden;">
+					<div class="heaps-scene"></div>
+					<div class="image-properties">
+						<div class="title">MSDF infos</div>
+						<div class="msdf-infos">
+							<p class="tex-weight">Texture weight : missing info </p>
+							<p class="current-size">Current size : 0 px </p>
+							<p>Resize : 
+								<input type="number" class="size" value=0 style="width:7ch;"></input>
+								<input type="button" class="save-size" value="Save" title="Save current size options into a props.json file."/>
+							</p>
+						</div>
+					</div>
+				</div>
+				<div class="identifiers">
+					<label>MSDF</label>
+					<label>Raw</label>
+				</div>
+			</div>
+		');
+
+		scene = new hide.comp.Scene(config, null, element.find(".heaps-scene"));
+		var msdfInfos = element.find(".msdf-infos");
+		var resize = msdfInfos.find(".size");
+		var currentSizeField = msdfInfos.find(".current-size");
+
+		var fs:hxd.fs.LocalFileSystem = Std.downcast(hxd.res.Loader.currentInstance.fs, hxd.fs.LocalFileSystem);
+
+		var dirPos = state.path.lastIndexOf("/");
+		var dirPath = dirPos < 0 ? state.path : state.path.substr(0, dirPos + 1);
+		var name = dirPos < 0 ? state.path : state.path.substr(dirPos + 1);
+		var propsFilePath = ide.getPath(dirPath + "props.json");
+
+		var saveSize = element.find(".save-size");
+		saveSize.on("click", function(_) {
+			if (resize.val() == currentSize)
+				return;
+
+			var bytes = new haxe.io.BytesOutput();
+			var convertRule = { convert : "png", priority: 10000000 };
+
+				Reflect.setField(convertRule, "size", resize.val());
+
+			if (sys.FileSystem.exists(propsFilePath)) {
+				var propsJson = haxe.Json.parse(sys.io.File.getContent(propsFilePath));
+
+				if (Reflect.hasField(propsJson, "fs.convert")) {
+					var fsConvertObj = Reflect.getProperty(propsJson, "fs.convert");
+					Reflect.setField(fsConvertObj, state.path, convertRule);
+				}
+				else {
+					var fsConvertObj = {} ;
+					Reflect.setField(fsConvertObj, state.path, convertRule);
+					Reflect.setProperty(propsJson, "fs.convert", fsConvertObj);
+				}
+
+				var data = haxe.Json.stringify(propsJson, "\t");
+				bytes.writeString(data);
+				hxd.File.saveBytes(propsFilePath, bytes.getBytes());
+			} else {
+				var fsConvertObj = { };
+				var pathObj = { };
+
+				Reflect.setProperty(pathObj, state.path, convertRule);
+				Reflect.setProperty(fsConvertObj, "fs.convert", pathObj);
+				var data = haxe.Json.stringify(fsConvertObj, "\t");
+				bytes.writeString(data);
+				hxd.File.saveBytes(propsFilePath, bytes.getBytes());
+			}
+
+			@:privateAccess fs.convert.configs.clear();
+			@:privateAccess fs.convert.loadConfig(state.path);
+
+			var localEntry = @:privateAccess new hxd.fs.LocalFileSystem.LocalEntry(fs, name, state.path, Ide.inst.getPath(state.path));
+			fs.convert.run(localEntry);
+			currentSize = resize.val();
+			currentSizeField.text('Current size : ${currentSize} px');
+
+			replaceTexture(@:privateAccess localEntry.file);
+		});
+
+		this.saveDisplayKey = state.path;
+		this.viewMode = getDisplayState("ViewMode");
+		if (this.viewMode == null)
+			this.viewMode = MSDF;
+
+		var identifiers = element.find(".identifiers");
+		identifiers.css(this.viewMode.match(MSDFViewMode.Comparison) ? {"visibility":"inherit"} : {"visibility":"hidden"});
+
+		shader = new MSDFViewerShader();
+		tools = new hide.comp.Toolbar(null,element.find(".toolbar"));
+
+		tools.addSeparator();
+
+		var tgMSDF = tools.addToggle("show-msdf", "file-zip-o", "Show MSDF", "", function (e) {
+			tools.element.find(".show-raw").removeAttr("checked");
+			tools.element.find(".show-comparison").removeAttr("checked");
+
+			if (bmp != null) {
+				this.saveDisplayState("ViewMode", MSDF);
+				this.viewMode = MSDF;
+
+				var identifiers = element.find(".identifiers");
+				identifiers.css(this.viewMode.match(MSDFViewMode.Comparison) ? {"visibility":"inherit"} : {"visibility":"hidden"});
+
+				applyShaderConfiguration();
+			}
+		}, this.viewMode.match(MSDF), null, false);
+		tgMSDF.element.addClass("show-msdf");
+
+		var tgRaw = tools.addToggle("show-raw","file-image-o", "Show Raw", "", function (e) {
+			tools.element.find(".show-msdf").removeAttr("checked");
+			tools.element.find(".show-comparison").removeAttr("checked");
+
+			if (bmp != null) {
+				this.saveDisplayState("ViewMode", Raw);
+				this.viewMode = Raw;
+
+				var identifiers = element.find(".identifiers");
+				identifiers.css(this.viewMode.match(MSDFViewMode.Comparison) ? {"visibility":"inherit"} : {"visibility":"hidden"});
+
+				applyShaderConfiguration();
+			}
+
+		}, this.viewMode.match(Raw), null, false);
+		tgRaw.element.addClass("show-raw");
+
+		var tgComparison = tools.addToggle("show-comparison","arrows-h", "Show comparison between MSDF and Raw", "", function (e) {
+			tools.element.find(".show-raw").removeAttr("checked");
+			tools.element.find(".show-msdf").removeAttr("checked");
+
+			if (bmp != null) {
+				this.saveDisplayState("ViewMode", Comparison);
+				this.viewMode = Comparison;
+
+				var identifiers = element.find(".identifiers");
+				identifiers.css(this.viewMode.match(MSDFViewMode.Comparison) ? {"visibility":"inherit"} : {"visibility":"hidden"});
+
+				applyShaderConfiguration();
+			}
+
+		}, this.viewMode.match(Comparison), null, false);
+		tgComparison.element.addClass("show-comparison");
+
+		tools.addSeparator();
+
+		// We don't want to load old texture from cache because convert rule might
+		// have been changed
+		@:privateAccess fs.fileCache.remove(state.path);
+
+		scene.onReady = function() {
+			scene.loadTexture(state.path, state.path, function(texture) {
+				onTextureLoaded(texture);
+			}, false);
+		};
+	}
+
+	override function onActivate() {
+		if (tools != null)
+			tools.refreshToggles();
+	}
+
+	override function onRebuild() {
+		if ( scene != null ) {
+			scene.dispose();
+			scene = null;
+		}
+		super.onRebuild();
+	}
+
+	override function onResize() {
+		if( bmp == null ) return;
+
+		var scale = Math.min(1,Math.min((contentWidth - 20) / bmp.tile.width, (contentHeight - 20) / bmp.tile.height));
+
+		var cam2d = Std.downcast(cam, hide.view.l3d.CameraController2D);
+		if (cam2d != null) {
+			@:privateAccess cam2d.curPos.set(bmp.tile.width / 2, bmp.tile.width / 2, (1 / bmp.tile.width) * 500);
+		}
+		else {
+			bmp.setScale(scale * js.Browser.window.devicePixelRatio);
+			bmp.x = -Std.int(bmp.tile.width * bmp.scaleX) >> 1;
+			bmp.y = -Std.int(bmp.tile.height * bmp.scaleY) >> 1;
+		}
+
+		updateSliderVisual();
+	}
+
+	public function onTextureLoaded(texture: Null<h3d.mat.Texture>) {
+		scene.element.on("wheel", function(_) {
+			updateSliderVisual();
+		});
+
+		bmp = new h2d.Bitmap(h2d.Tile.fromTexture(texture), scene.s2d);
+		bmp.smooth = true;
+		bmp.addShader(shader);
+		shader.texture = texture;
+		shader.blur = 1.0;
+		shader.comparisonFactor = 0.5;
+		this.cam = new hide.view.l3d.CameraController2D(scene.s2d);
+
+		var texMemSize = element.find(".tex-weight");
+		texMemSize.text('Texture weight : ${@:privateAccess floatToStringPrecision(texture.mem.memSize(texture) / (1024 * 1024)) } mb');
+
+		currentSize = texture.width;
+		
+		var resize = element.find(".resize");
+		resize.val(currentSize);
+		var currentSizeField = element.find(".current-size");
+		currentSizeField.text('Current size : ${currentSize} px');
+
+		applyShaderConfiguration();
+		onResize();
+	}
+
+	public function applyShaderConfiguration() {
+		switch (this.viewMode) {
+			case  MSDF:
+				{
+					shader.comparisonFactor = 1;
+
+					if (interactive != null)
+						interactive.remove();
+
+					if (sliderBmp != null)
+						sliderBmp.alpha = 0;
+				}
+
+			case Raw:
+				{
+					shader.comparisonFactor = 0;
+
+					if (interactive != null)
+						interactive.remove();
+
+					if (sliderBmp != null)
+						sliderBmp.alpha = 0;
+				}
+
+			case Comparison:
+				{
+					if (sliderBmp == null)
+						drawSlider();
+					else
+						sliderBmp.alpha = 1;
+
+					bmp.addChild(sliderBmp);
+
+					var bounds = new h2d.col.Bounds();
+					sliderBmp.getSize(bounds);
+
+					if (interactive != null)
+						interactive.remove();
+
+					interactive = new h2d.Interactive(bmp.tile.width,bmp.tile.height,bmp);
+					interactive.propagateEvents = true;
+					interactive.x = bmp.tile.dx;
+					interactive.y = bmp.tile.dy;
+
+					sliderBmp.x = bmp.tile.width / 2.0 - bounds.width / 2.0;
+					shader.comparisonFactor = 0.5;
+					var clicked = false;
+
+					function updateSlider(e: hxd.Event) {
+						if (!clicked)
+							return;
+						sliderBmp.x = e.relX - (bounds.width * sliderBmp.scaleX) / 2.0;
+						shader.comparisonFactor = e.relX / interactive.width;
+					}
+
+					interactive.onPush = function (e) {
+						clicked = true;
+						updateSlider(e);
+					}
+
+					interactive.onRelease = function (e) {
+						clicked = false;
+					}
+
+					interactive.onMove = function (e) {
+						updateSlider(e);
+					};
+				}
+		}
+	}
+
+	public function cleanUp() {
+		if (scene != null)
+			scene.dispose();
+
+		sliderBmp = null;
+
+		if (bmp != null && !bmp.tile.isDisposed())
+			bmp.tile.dispose();
+
+		bmp = null;
+		interactive = null;
+		shader = null;
+	}
+
+	public function floatToStringPrecision(number:Float, ?precision=2) {
+		number *= Math.pow(10, precision);
+		return Math.round(number) / Math.pow(10, precision);
+	}
+
+	public function updateSliderVisual() {
+		var cam2d = Std.downcast(cam, hide.view.l3d.CameraController2D);
+		if (cam2d != null && sliderBmp != null) {
+			var oldWidth = sliderBmp.getSize().width;
+			@:privateAccess sliderBmp.scaleX = (1 / (cam2d.curPos.z)) * 2;
+			var offset = sliderBmp.getSize().width - oldWidth;
+			sliderBmp.x -= offset / 4;
+		}
+
+		// todo : handle slider zoom for cam 3d
+	}
+
+	public function drawSlider() {
+		if (sliderBmp == null)
+			sliderBmp = new h2d.Graphics(scene.s2d);
+
+		sliderBmp.clear();
+		sliderBmp.beginFill(0xFFFFFF, 1);
+		sliderBmp.drawRect(0,0,2, shader.texture.height);
+		sliderBmp.endFill();
+
+		updateSliderVisual();
+	}
+
+	public function replaceTexture(path : String) {
+		var bytes = sys.io.File.getBytes(path);
+		var res = hxd.res.Any.fromBytes(path, bytes);
+		var texture = res.toTexture();
+
+		if (bmp != null) {
+			if (!bmp.tile.isDisposed())
+				bmp.tile.dispose();
+			bmp.remove();
+			bmp = null;
+		}
+
+		tools.element.find(".hide-range").remove();
+
+		bmp = new h2d.Bitmap(h2d.Tile.fromTexture(texture), scene.s2d);
+		bmp.smooth = true;
+		bmp.addShader(shader);
+		shader.texture = texture;
+		shader.blur = 1.0;
+		shader.comparisonFactor = 0.5;
+		this.cam = new hide.view.l3d.CameraController2D(scene.s2d);
+
+		var texMemSize = element.find(".tex-weight");
+		texMemSize.text('Texture weight : ${@:privateAccess floatToStringPrecision(texture.mem.memSize(texture) / (1024 * 1024)) } mb');
+		currentSize = texture.width;
+		var resize = element.find(".resize");
+		resize.val(currentSize);
+		var currentSizeField = element.find(".current-size");
+		currentSizeField.text('Current size : ${currentSize} px');
+
+		applyShaderConfiguration();
+		onResize();
+	}
+
+	static var _ = FileTree.registerExtension(MSDFView,["svg"],{ icon : "picture-o" });
+
+}