mirror of
				https://github.com/MaddyThorson/StrawberryBF.git
				synced 2025-11-04 01:41:33 +08:00 
			
		
		
		
	Some aseprite loading work. Still need to solve decompression
This commit is contained in:
		@ -15,6 +15,7 @@ namespace Strawberry
 | 
			
		||||
 | 
			
		||||
	public class Game
 | 
			
		||||
	{
 | 
			
		||||
		public readonly Dictionary<String, Sprite> Sprites;
 | 
			
		||||
		public readonly List<VirtualInput> VirtualInputs;
 | 
			
		||||
		public readonly String Title;
 | 
			
		||||
		public readonly int Width;
 | 
			
		||||
@ -42,7 +43,6 @@ namespace Strawberry
 | 
			
		||||
			: base()
 | 
			
		||||
		{
 | 
			
		||||
			Game = this;
 | 
			
		||||
			VirtualInputs = new List<VirtualInput>();
 | 
			
		||||
 | 
			
		||||
			Title = windowTitle;
 | 
			
		||||
			Width = width;
 | 
			
		||||
@ -81,8 +81,13 @@ namespace Strawberry
 | 
			
		||||
			SDLMixer.OpenAudio(44100, SDLMixer.MIX_DEFAULT_FORMAT, 2, 4096);
 | 
			
		||||
			SDLTTF.Init();
 | 
			
		||||
 | 
			
		||||
			BuildTypeLists();
 | 
			
		||||
			VirtualInputs = new List<VirtualInput>();
 | 
			
		||||
			Input.[Friend]Init(gamepadLimit);
 | 
			
		||||
 | 
			
		||||
			Sprites = new Dictionary<String, Sprite>();
 | 
			
		||||
			//LoadSprites();
 | 
			
		||||
 | 
			
		||||
			BuildTypeLists();
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public ~this()
 | 
			
		||||
@ -104,6 +109,8 @@ namespace Strawberry
 | 
			
		||||
 | 
			
		||||
			DisposeTypeLists();
 | 
			
		||||
			Input.[Friend]Dispose();
 | 
			
		||||
			DisposeSprites();
 | 
			
		||||
			Sprite.[Friend]Dispose();
 | 
			
		||||
			Game = null;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
@ -184,7 +191,7 @@ namespace Strawberry
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public void Render()
 | 
			
		||||
		private void Render()
 | 
			
		||||
		{
 | 
			
		||||
			SDL.SetRenderDrawColor(Renderer, ClearColor.R, ClearColor.G, ClearColor.B, ClearColor.A);
 | 
			
		||||
			SDL.RenderClear(Renderer);
 | 
			
		||||
@ -214,6 +221,51 @@ namespace Strawberry
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Load Images
 | 
			
		||||
 | 
			
		||||
		private void LoadSprites()
 | 
			
		||||
		{
 | 
			
		||||
			let root = scope String(ContentRoot);
 | 
			
		||||
			root.Append(Path.DirectorySeparatorChar);
 | 
			
		||||
			root.Append("Sprites");
 | 
			
		||||
			if (Directory.Exists(root))
 | 
			
		||||
				LoadSpritesDir(root);
 | 
			
		||||
			else
 | 
			
		||||
				Console.WriteLine("Content/Sprites folder does not exist!");
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		private void LoadSpritesDir(String directory)
 | 
			
		||||
		{
 | 
			
		||||
			for (let dir in Directory.EnumerateDirectories(directory))
 | 
			
		||||
			{
 | 
			
		||||
				let path = scope String();
 | 
			
		||||
				dir.GetFilePath(path);
 | 
			
		||||
				LoadSpritesDir(path);
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			for (let file in Directory.EnumerateFiles(directory, "*.ase"))
 | 
			
		||||
			{
 | 
			
		||||
				let path = scope String();
 | 
			
		||||
				file.GetFilePath(path);
 | 
			
		||||
 | 
			
		||||
				let sprite = new [Friend]Sprite(path);
 | 
			
		||||
 | 
			
		||||
				path.Remove(0, ContentRoot.Length + 8);
 | 
			
		||||
				Sprites.Add(new String(path), sprite);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		private void DisposeSprites()
 | 
			
		||||
		{
 | 
			
		||||
			for (let kv in Sprites)
 | 
			
		||||
			{
 | 
			
		||||
				delete kv.key;
 | 
			
		||||
				delete kv.value;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			delete Sprites;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Type assignable caching
 | 
			
		||||
 | 
			
		||||
		private void BuildTypeLists()
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										604
									
								
								src/Core/Sprite.bf
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										604
									
								
								src/Core/Sprite.bf
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,604 @@
 | 
			
		||||
using System;
 | 
			
		||||
using SDL2;
 | 
			
		||||
using System.IO;
 | 
			
		||||
using System.Text;
 | 
			
		||||
using System.Collections;
 | 
			
		||||
using MiniZ;
 | 
			
		||||
 | 
			
		||||
namespace Strawberry
 | 
			
		||||
{
 | 
			
		||||
	public class Sprite
 | 
			
		||||
	{
 | 
			
		||||
		private enum Modes
 | 
			
		||||
		{
 | 
			
		||||
		    Indexed = 1,
 | 
			
		||||
		    Grayscale = 2,
 | 
			
		||||
		    RGBA = 4,
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		private enum Chunks
 | 
			
		||||
		{
 | 
			
		||||
		    OldPaletteA = 0x0004,
 | 
			
		||||
		    OldPaletteB = 0x0011,
 | 
			
		||||
		    Layer = 0x2004,
 | 
			
		||||
		    Cel = 0x2005,
 | 
			
		||||
		    CelExtra = 0x2006,
 | 
			
		||||
		    Mask = 0x2016,
 | 
			
		||||
		    Path = 0x2017,
 | 
			
		||||
		    FrameTags = 0x2018,
 | 
			
		||||
		    Palette = 0x2019,
 | 
			
		||||
		    UserData = 0x2020,
 | 
			
		||||
		    Slice = 0x2022
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public readonly String Path;
 | 
			
		||||
 | 
			
		||||
		private Frame[] frames;
 | 
			
		||||
		private List<Layer> layers;
 | 
			
		||||
		private List<Tag> tags;
 | 
			
		||||
		private List<Slice> slices;
 | 
			
		||||
		private int width;
 | 
			
		||||
		private int height;
 | 
			
		||||
		private Modes mode;
 | 
			
		||||
 | 
			
		||||
		private this(String path)
 | 
			
		||||
		{
 | 
			
		||||
			Path = new String(path);
 | 
			
		||||
			Load();
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public ~this()
 | 
			
		||||
		{
 | 
			
		||||
			delete Path;
 | 
			
		||||
			Unload();
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		private void Unload()
 | 
			
		||||
		{
 | 
			
		||||
			for (let f in frames)
 | 
			
		||||
				delete f;
 | 
			
		||||
			delete frames;
 | 
			
		||||
 | 
			
		||||
			for (let l in layers)
 | 
			
		||||
				delete l;
 | 
			
		||||
			delete layers;
 | 
			
		||||
 | 
			
		||||
			for (let t in tags)
 | 
			
		||||
				delete t;
 | 
			
		||||
			delete tags;
 | 
			
		||||
 | 
			
		||||
			for (let s in slices)
 | 
			
		||||
				delete s;
 | 
			
		||||
			delete slices;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public void Reload()
 | 
			
		||||
		{
 | 
			
		||||
			Unload();
 | 
			
		||||
			Load();
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		private void Load()
 | 
			
		||||
		{
 | 
			
		||||
			/*
 | 
			
		||||
				Aseprite file loading based on code from Noel Berry's Foster Framework here:
 | 
			
		||||
				https://github.com/NoelFB/Foster/blob/master/Framework/Graphics/Images/Aseprite.cs
 | 
			
		||||
			*/
 | 
			
		||||
 | 
			
		||||
			let stream = scope FileStream();
 | 
			
		||||
			stream.Open(Path, .Read, .Read);
 | 
			
		||||
 | 
			
		||||
			//Helpers to match ASE file format spec
 | 
			
		||||
			uint8 BYTE() => stream.Read<uint8>();
 | 
			
		||||
			uint16 WORD() => stream.Read<uint16>();
 | 
			
		||||
			int16 SHORT() => stream.Read<int16>();
 | 
			
		||||
			uint32 DWORD() => stream.Read<uint32>();
 | 
			
		||||
			int32 LONG() => stream.Read<int32>();
 | 
			
		||||
			void SEEK(int bytes) => stream.Position += bytes;
 | 
			
		||||
			void BYTES(uint8[] into, int bytes)
 | 
			
		||||
			{
 | 
			
		||||
				for (let i < bytes)
 | 
			
		||||
					into[i] = BYTE();
 | 
			
		||||
			}
 | 
			
		||||
			String STRING(String into)
 | 
			
		||||
			{
 | 
			
		||||
				let len = WORD();
 | 
			
		||||
				let arr = scope uint8[len];
 | 
			
		||||
				for (let i < len)
 | 
			
		||||
					arr[i] = BYTE();
 | 
			
		||||
 | 
			
		||||
				Encoding.UTF8.DecodeToUTF8(arr, into);
 | 
			
		||||
				return into;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			//Parse
 | 
			
		||||
			{
 | 
			
		||||
				//Header
 | 
			
		||||
				{
 | 
			
		||||
					//File Size
 | 
			
		||||
					DWORD();
 | 
			
		||||
 | 
			
		||||
					// Magic number
 | 
			
		||||
					var magic = WORD();
 | 
			
		||||
					if (magic != 0xA5E0)
 | 
			
		||||
						Runtime.FatalError("File is not in .ase format");
 | 
			
		||||
 | 
			
		||||
					// Frame Count / Width / Height / Color Mode
 | 
			
		||||
					frames = new Frame[WORD()];
 | 
			
		||||
					width = WORD();
 | 
			
		||||
					height = WORD();
 | 
			
		||||
					mode = (Modes)(WORD() / 8);
 | 
			
		||||
 | 
			
		||||
					// Other Info, Ignored
 | 
			
		||||
					DWORD();       // Flags
 | 
			
		||||
					WORD();        // Speed (deprecated)
 | 
			
		||||
					DWORD();       // Set be 0
 | 
			
		||||
					DWORD();       // Set be 0
 | 
			
		||||
					BYTE();        // Palette entry 
 | 
			
		||||
					SEEK(3);       // Ignore these bytes
 | 
			
		||||
					WORD();        // Number of colors (0 means 256 for old sprites)
 | 
			
		||||
					BYTE();        // Pixel width
 | 
			
		||||
					BYTE();        // Pixel height
 | 
			
		||||
					SEEK(92);      // For Future
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				layers = new List<Layer>();
 | 
			
		||||
				tags = new List<Tag>();
 | 
			
		||||
				slices = new List<Slice>();
 | 
			
		||||
 | 
			
		||||
				// Body
 | 
			
		||||
				{
 | 
			
		||||
					var temp = scope:: uint8[width * height * (int)mode];
 | 
			
		||||
					let palette = scope:: Color[256];
 | 
			
		||||
					HasUserData last = null;
 | 
			
		||||
 | 
			
		||||
					for (int i = 0; i < frames.Count; i++)
 | 
			
		||||
					{
 | 
			
		||||
					    let frame = new Frame(width, height);
 | 
			
		||||
					    frames[i] = frame;
 | 
			
		||||
 | 
			
		||||
					    int64 frameStart, frameEnd;
 | 
			
		||||
					    int chunkCount;
 | 
			
		||||
 | 
			
		||||
					    // frame header
 | 
			
		||||
					    {
 | 
			
		||||
					        frameStart = stream.Position;
 | 
			
		||||
					        frameEnd = frameStart + DWORD();
 | 
			
		||||
					        WORD();                  // Magic number (always 0xF1FA)
 | 
			
		||||
					        chunkCount = WORD();     // Number of "chunks" in this frame
 | 
			
		||||
					        frame.Duration = WORD(); // Frame duration (in milliseconds)
 | 
			
		||||
					        SEEK(6);                 // For future (set to zero)
 | 
			
		||||
					    }
 | 
			
		||||
 | 
			
		||||
					    // chunks
 | 
			
		||||
					    for (int j = 0; j < chunkCount; j++)
 | 
			
		||||
					    {
 | 
			
		||||
					        int64 chunkStart, chunkEnd;
 | 
			
		||||
					        Chunks chunkType;
 | 
			
		||||
 | 
			
		||||
					        // chunk header
 | 
			
		||||
					        {
 | 
			
		||||
					            chunkStart = stream.Position;
 | 
			
		||||
					            chunkEnd = chunkStart + DWORD();
 | 
			
		||||
					            chunkType = (Chunks)WORD();
 | 
			
		||||
					        }
 | 
			
		||||
 | 
			
		||||
					        // LAYER CHUNK
 | 
			
		||||
					        if (chunkType == Chunks.Layer)
 | 
			
		||||
					        {
 | 
			
		||||
					            // create layer
 | 
			
		||||
					            var layer = new Layer();
 | 
			
		||||
 | 
			
		||||
					            // get layer data
 | 
			
		||||
					            layer.Flag = (Layer.Flags)WORD();
 | 
			
		||||
					            layer.Type = (Layer.Types)WORD();
 | 
			
		||||
					            layer.ChildLevel = WORD();
 | 
			
		||||
					            WORD(); // width (unused)
 | 
			
		||||
					            WORD(); // height (unused)
 | 
			
		||||
					            layer.BlendMode = WORD();
 | 
			
		||||
					            layer.Alpha = (BYTE() / 255f);
 | 
			
		||||
					            SEEK(3); // for future
 | 
			
		||||
					            STRING(layer.Name);
 | 
			
		||||
 | 
			
		||||
					            layers.Add(layer);
 | 
			
		||||
					        }
 | 
			
		||||
					        // CEL CHUNK
 | 
			
		||||
					        else if (chunkType == Chunks.Cel) Cel:
 | 
			
		||||
					        {
 | 
			
		||||
					            var layer = layers[WORD()];
 | 
			
		||||
					            var x = SHORT();
 | 
			
		||||
					            var y = SHORT();
 | 
			
		||||
					            var alpha = BYTE() / 255f;
 | 
			
		||||
					            var celType = WORD();
 | 
			
		||||
					            var width = 0;
 | 
			
		||||
					            var height = 0;
 | 
			
		||||
					            Color[] pixels = null;
 | 
			
		||||
					            Cel link = null;
 | 
			
		||||
 | 
			
		||||
					            SEEK(7);
 | 
			
		||||
 | 
			
		||||
					            // RAW or DEFLATE
 | 
			
		||||
					            if (celType == 0 || celType == 2)
 | 
			
		||||
					            {
 | 
			
		||||
					                width = WORD();
 | 
			
		||||
					                height = WORD();
 | 
			
		||||
 | 
			
		||||
					                var count = width * height * (int)mode;
 | 
			
		||||
					                if (count > temp.Count)
 | 
			
		||||
					                    temp = scope:: uint8[count];
 | 
			
		||||
 | 
			
		||||
					                // RAW
 | 
			
		||||
					                if (celType == 0)
 | 
			
		||||
					                {
 | 
			
		||||
										BYTES(temp, count);
 | 
			
		||||
					                }
 | 
			
		||||
					                // DEFLATE
 | 
			
		||||
					                else
 | 
			
		||||
					                {
 | 
			
		||||
										//TODO: Figure this out to enable aseprite loading
 | 
			
		||||
 | 
			
		||||
										Runtime.FatalError("Decompression not yet implemented.");
 | 
			
		||||
 | 
			
		||||
										/*
 | 
			
		||||
										Noel's C#:
 | 
			
		||||
					                    SEEK(2);
 | 
			
		||||
										
 | 
			
		||||
					                    using var deflate = new DeflateStream(reader.BaseStream, CompressionMode.Decompress, true);
 | 
			
		||||
					                    deflate.Read(temp, 0, count);
 | 
			
		||||
										*/
 | 
			
		||||
					                }
 | 
			
		||||
 | 
			
		||||
					                // get pixel data
 | 
			
		||||
					                pixels = scope:Cel Color[width * height];
 | 
			
		||||
					                BytesToPixels(temp, pixels, mode, palette);
 | 
			
		||||
					            }
 | 
			
		||||
					            // REFERENCE
 | 
			
		||||
					            else if (celType == 1)
 | 
			
		||||
					            {
 | 
			
		||||
					                var linkFrame = frames[WORD()];
 | 
			
		||||
					                var linkCel = linkFrame.Cels[frame.Cels.Count];
 | 
			
		||||
 | 
			
		||||
					                width = linkCel.Width;
 | 
			
		||||
					                height = linkCel.Height;
 | 
			
		||||
					                pixels = linkCel.Pixels;
 | 
			
		||||
					                link = linkCel;
 | 
			
		||||
					            }
 | 
			
		||||
					            else
 | 
			
		||||
									Runtime.FatalError("Cel type not yet implemented");
 | 
			
		||||
 | 
			
		||||
								var cel = new Cel(layer, pixels);
 | 
			
		||||
					            cel.X = x;
 | 
			
		||||
					            cel.Y = y;
 | 
			
		||||
					            cel.Width = width;
 | 
			
		||||
					            cel.Height = height;
 | 
			
		||||
					            cel.Alpha = alpha;
 | 
			
		||||
					            cel.Link = link;
 | 
			
		||||
 | 
			
		||||
					            // draw to frame if visible
 | 
			
		||||
					            if (cel.Layer.Visible)
 | 
			
		||||
					                CelToFrame(frame, cel);
 | 
			
		||||
 | 
			
		||||
					            frame.Cels.Add(cel);
 | 
			
		||||
					        }
 | 
			
		||||
					        // PALETTE CHUNK
 | 
			
		||||
					        else if (chunkType == Chunks.Palette)
 | 
			
		||||
					        {
 | 
			
		||||
					            DWORD(); //size (unused)
 | 
			
		||||
					            var start = DWORD();
 | 
			
		||||
					            var end = DWORD();
 | 
			
		||||
					            SEEK(8); // for future
 | 
			
		||||
 | 
			
		||||
					            for (int p = 0; p < (end - start) + 1; p++)
 | 
			
		||||
					            {
 | 
			
		||||
					                var hasName = WORD();
 | 
			
		||||
					                palette[start + p] = Color(BYTE(), BYTE(), BYTE(), BYTE());
 | 
			
		||||
 | 
			
		||||
					                if (Calc.BitCheck(hasName, 0))
 | 
			
		||||
					                    STRING(scope String());
 | 
			
		||||
					            }
 | 
			
		||||
					        }
 | 
			
		||||
					        // USERDATA
 | 
			
		||||
					        else if (chunkType == Chunks.UserData)
 | 
			
		||||
					        {
 | 
			
		||||
					            if (last != null)
 | 
			
		||||
					            {
 | 
			
		||||
					                var flags = (int)DWORD();
 | 
			
		||||
 | 
			
		||||
					                // has text
 | 
			
		||||
					                if (Calc.BitCheck(flags, 0))
 | 
			
		||||
					                    STRING(last.UserDataText);
 | 
			
		||||
 | 
			
		||||
					                // has color
 | 
			
		||||
					                if (Calc.BitCheck(flags, 1))
 | 
			
		||||
					                    last.UserDataColor = Color(BYTE(), BYTE(), BYTE(), BYTE());
 | 
			
		||||
					            }
 | 
			
		||||
					        }
 | 
			
		||||
					        // TAG
 | 
			
		||||
					        else if (chunkType == Chunks.FrameTags)
 | 
			
		||||
					        {
 | 
			
		||||
					            var count = WORD();
 | 
			
		||||
					            SEEK(8);
 | 
			
		||||
 | 
			
		||||
					            for (int t = 0; t < count; t++)
 | 
			
		||||
					            {
 | 
			
		||||
					                var tag = new Tag();
 | 
			
		||||
					                tag.From = WORD();
 | 
			
		||||
					                tag.To = WORD();
 | 
			
		||||
					                tag.LoopDirection = (Tag.LoopDirections)BYTE();
 | 
			
		||||
					                SEEK(8);
 | 
			
		||||
					                tag.Color = Color(BYTE(), BYTE(), BYTE(), (uint8)255);
 | 
			
		||||
					                SEEK(1);
 | 
			
		||||
					                STRING(tag.Name);
 | 
			
		||||
					                tags.Add(tag);
 | 
			
		||||
					            }
 | 
			
		||||
					        }
 | 
			
		||||
					        // SLICE
 | 
			
		||||
					        else if (chunkType == Chunks.Slice)
 | 
			
		||||
					        {
 | 
			
		||||
					            var count = DWORD();
 | 
			
		||||
					            var flags = (int)DWORD();
 | 
			
		||||
					            DWORD(); // reserved
 | 
			
		||||
					            var name = STRING(scope String());
 | 
			
		||||
 | 
			
		||||
					            for (int s = 0; s < count; s++)
 | 
			
		||||
					            {
 | 
			
		||||
					                var slice = new Slice();
 | 
			
		||||
									slice.Name.Set(name);
 | 
			
		||||
				                    slice.Frame = (int)DWORD();
 | 
			
		||||
				                    slice.OriginX = (int)LONG();
 | 
			
		||||
				                    slice.OriginY = (int)LONG();
 | 
			
		||||
				                    slice.Width = (int)DWORD();
 | 
			
		||||
				                    slice.Height = (int)DWORD();
 | 
			
		||||
 | 
			
		||||
					                // 9 slice (ignored atm)
 | 
			
		||||
					                if (Calc.BitCheck(flags, 0))
 | 
			
		||||
					                {
 | 
			
		||||
					                    slice.NineSlice = Rect(
 | 
			
		||||
					                        (int)LONG(),
 | 
			
		||||
					                        (int)LONG(),
 | 
			
		||||
					                        (int)DWORD(),
 | 
			
		||||
					                        (int)DWORD());
 | 
			
		||||
					                }
 | 
			
		||||
 | 
			
		||||
					                // pivot point
 | 
			
		||||
					                if (Calc.BitCheck(flags, 1))
 | 
			
		||||
					                    slice.Pivot = Point((int)DWORD(), (int)DWORD());
 | 
			
		||||
					                
 | 
			
		||||
					                slices.Add(slice);
 | 
			
		||||
					            }
 | 
			
		||||
					        }
 | 
			
		||||
 | 
			
		||||
					        stream.Position = chunkEnd;
 | 
			
		||||
					    }
 | 
			
		||||
 | 
			
		||||
					    stream.Position = frameEnd;
 | 
			
		||||
						frame.FinishLoad();
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			stream.Close();
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		private void BytesToPixels(uint8[] bytes, Color[] pixels, Modes mode, Color[] palette)
 | 
			
		||||
		{
 | 
			
		||||
		    int len = pixels.Count;
 | 
			
		||||
		    if (mode == Modes.RGBA)
 | 
			
		||||
		    {
 | 
			
		||||
		        for (int p = 0, int b = 0; p < len; p++, b += 4)
 | 
			
		||||
		        {
 | 
			
		||||
		            pixels[p].R = (uint8)(bytes[b + 0] * bytes[b + 3] / 255);
 | 
			
		||||
		            pixels[p].G = (uint8)(bytes[b + 1] * bytes[b + 3] / 255);
 | 
			
		||||
		            pixels[p].B = (uint8)(bytes[b + 2] * bytes[b + 3] / 255);
 | 
			
		||||
		            pixels[p].A = bytes[b + 3];
 | 
			
		||||
		        }
 | 
			
		||||
		    }
 | 
			
		||||
		    else if (mode == Modes.Grayscale)
 | 
			
		||||
		    {
 | 
			
		||||
		        for (int p = 0, int b = 0; p < len; p++, b += 2)
 | 
			
		||||
		        {
 | 
			
		||||
		            pixels[p].R = pixels[p].G = pixels[p].B = (uint8)(bytes[b + 0] * bytes[b + 1] / 255);
 | 
			
		||||
		            pixels[p].A = bytes[b + 1];
 | 
			
		||||
		        }
 | 
			
		||||
		    }
 | 
			
		||||
		    else if (mode == Modes.Indexed)
 | 
			
		||||
		    {
 | 
			
		||||
		        for (int p = 0;  p < len; p++)
 | 
			
		||||
		            pixels[p] = palette[bytes[p]];
 | 
			
		||||
		    }
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		private void CelToFrame(Frame frame, Cel cel)
 | 
			
		||||
		{
 | 
			
		||||
		    let opacity = (uint8)((cel.Alpha * cel.Layer.Alpha) * 255);
 | 
			
		||||
		    var pxLen = frame.PixelsLength;
 | 
			
		||||
 | 
			
		||||
		    var blend = BlendModes[0];
 | 
			
		||||
		    if (cel.Layer.BlendMode < BlendModes.Count)
 | 
			
		||||
		        blend = BlendModes[cel.Layer.BlendMode];
 | 
			
		||||
 | 
			
		||||
		    for (int sx = Math.Max(0, -cel.X), int right = Math.Min(cel.Width, width - cel.X); sx < right; sx++)
 | 
			
		||||
		    {
 | 
			
		||||
		        int dx = cel.X + sx;
 | 
			
		||||
		        int dy = cel.Y * width;
 | 
			
		||||
 | 
			
		||||
		        for (int sy = Math.Max(0, -cel.Y), int bottom = Math.Min(cel.Height, height - cel.Y); sy < bottom; sy++, dy += width)
 | 
			
		||||
		        {
 | 
			
		||||
		            if (dx + dy >= 0 && dx + dy < pxLen)
 | 
			
		||||
		                blend(frame.Pixels, (dx + dy) * 4, cel.Pixels[sx + sy * cel.Width], opacity);
 | 
			
		||||
		        }
 | 
			
		||||
		    }
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Data
 | 
			
		||||
 | 
			
		||||
		private class Frame
 | 
			
		||||
		{
 | 
			
		||||
			public SDL.Texture* Texture;
 | 
			
		||||
			public float Duration;
 | 
			
		||||
			public List<Cel> Cels = new List<Cel>() ~ delete _;
 | 
			
		||||
			public uint8* Pixels;
 | 
			
		||||
			public int32 PixelsLength;
 | 
			
		||||
 | 
			
		||||
			public this(int w, int h)
 | 
			
		||||
			{
 | 
			
		||||
				Texture = SDL.CreateTexture(Game.Renderer, (uint32)SDL.PIXELFORMAT_ARGB8888, (int32)SDL.TextureAccess.Static, (int32)w, (int32)h);
 | 
			
		||||
 | 
			
		||||
				void* ptr;
 | 
			
		||||
				SDL.LockTexture(Texture, null, out ptr, out PixelsLength);
 | 
			
		||||
				Pixels = (uint8*)ptr;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			public ~this()
 | 
			
		||||
			{
 | 
			
		||||
				SDL.DestroyTexture(Texture);
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			public void FinishLoad()
 | 
			
		||||
			{
 | 
			
		||||
				SDL.UnlockTexture(Texture);
 | 
			
		||||
				Pixels = null;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public class Tag
 | 
			
		||||
		{
 | 
			
		||||
		    public enum LoopDirections
 | 
			
		||||
		    {
 | 
			
		||||
		        Forward = 0,
 | 
			
		||||
		        Reverse = 1,
 | 
			
		||||
		        PingPong = 2
 | 
			
		||||
		    }
 | 
			
		||||
 | 
			
		||||
		    public String Name = new String() ~ delete _;
 | 
			
		||||
		    public LoopDirections LoopDirection;
 | 
			
		||||
		    public int From;
 | 
			
		||||
		    public int To;
 | 
			
		||||
		    public Color Color;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public class HasUserData
 | 
			
		||||
		{
 | 
			
		||||
			public String UserDataText = new String("") ~ delete _;
 | 
			
		||||
		    public Color UserDataColor;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		private class Slice : HasUserData
 | 
			
		||||
		{
 | 
			
		||||
		    public int Frame;
 | 
			
		||||
		    public String Name = new String() ~ delete _;
 | 
			
		||||
		    public int OriginX;
 | 
			
		||||
		    public int OriginY;
 | 
			
		||||
		    public int Width;
 | 
			
		||||
		    public int Height;
 | 
			
		||||
		    public Point? Pivot;
 | 
			
		||||
		    public Rect? NineSlice;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		private class Cel : HasUserData
 | 
			
		||||
		{
 | 
			
		||||
		    public Layer Layer;
 | 
			
		||||
		    public Color[] Pixels;
 | 
			
		||||
		    public Cel Link;
 | 
			
		||||
 | 
			
		||||
		    public int X;
 | 
			
		||||
		    public int Y;
 | 
			
		||||
		    public int Width;
 | 
			
		||||
		    public int Height;
 | 
			
		||||
		    public float Alpha;
 | 
			
		||||
 | 
			
		||||
		    public this(Layer layer, Color[] pixels)
 | 
			
		||||
		    {
 | 
			
		||||
		        Layer = layer;
 | 
			
		||||
		        Pixels = pixels;
 | 
			
		||||
		    }
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		private class Layer : HasUserData
 | 
			
		||||
		{
 | 
			
		||||
		    public enum Flags
 | 
			
		||||
		    {
 | 
			
		||||
		        Visible = 1,
 | 
			
		||||
		        Editable = 2,
 | 
			
		||||
		        LockMovement = 4,
 | 
			
		||||
		        Background = 8,
 | 
			
		||||
		        PreferLinkedCels = 16,
 | 
			
		||||
		        Collapsed = 32,
 | 
			
		||||
		        Reference = 64
 | 
			
		||||
		    }
 | 
			
		||||
 | 
			
		||||
		    public enum Types
 | 
			
		||||
		    {
 | 
			
		||||
		        Normal = 0,
 | 
			
		||||
		        Group = 1
 | 
			
		||||
		    }
 | 
			
		||||
 | 
			
		||||
		    public Flags Flag;
 | 
			
		||||
		    public Types Type;
 | 
			
		||||
		    public String Name = new String() ~ delete _;
 | 
			
		||||
		    public int ChildLevel;
 | 
			
		||||
		    public int BlendMode;
 | 
			
		||||
		    public float Alpha;
 | 
			
		||||
		    public bool Visible { get { return Flag.HasFlag(Flags.Visible); } }
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Blend Modes
 | 
			
		||||
 | 
			
		||||
		// More or less copied from Aseprite's source code:
 | 
			
		||||
		// https://github.com/aseprite/aseprite/blob/master/src/doc/blend_funcs.cpp
 | 
			
		||||
 | 
			
		||||
		private delegate void Blend(uint8* dest, int index, Color src, uint8 opacity);
 | 
			
		||||
 | 
			
		||||
		private static void Dispose()
 | 
			
		||||
		{
 | 
			
		||||
			for (let b in BlendModes)
 | 
			
		||||
				delete b;
 | 
			
		||||
			delete BlendModes;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		private static readonly Blend[] BlendModes = new Blend[]
 | 
			
		||||
		{
 | 
			
		||||
		    // 0 - NORMAL
 | 
			
		||||
		    new (dest, index, src, opacity) =>
 | 
			
		||||
		    {
 | 
			
		||||
		        if (src.A != 0)
 | 
			
		||||
		        {
 | 
			
		||||
					uint8 a = dest[index];
 | 
			
		||||
					uint8 r = dest[index + 1];
 | 
			
		||||
					uint8 g = dest[index + 2];
 | 
			
		||||
					uint8 b = dest[index + 3];
 | 
			
		||||
 | 
			
		||||
		            if (dest[index] == 0)
 | 
			
		||||
		            {
 | 
			
		||||
		                a = src.A;
 | 
			
		||||
						r = src.R;
 | 
			
		||||
						g = src.G;
 | 
			
		||||
						b = src.B;
 | 
			
		||||
		            }
 | 
			
		||||
		            else
 | 
			
		||||
		            {
 | 
			
		||||
		                var sa = MUL_UN8(src.A, opacity);
 | 
			
		||||
		                var ra = a + sa - MUL_UN8(a, sa);
 | 
			
		||||
 | 
			
		||||
						a = (uint8)ra;
 | 
			
		||||
		                r = (uint8)(r + (src.R - r) * sa / ra);
 | 
			
		||||
		                g = (uint8)(g + (src.G - g) * sa / ra);
 | 
			
		||||
		                b = (uint8)(b + (src.B - b) * sa / ra);
 | 
			
		||||
		            }
 | 
			
		||||
 | 
			
		||||
					dest[index] = a;
 | 
			
		||||
					dest[index + 1] = r;
 | 
			
		||||
					dest[index + 2] = g;
 | 
			
		||||
					dest[index + 3] = b;
 | 
			
		||||
		        }
 | 
			
		||||
		    }
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		[Inline]
 | 
			
		||||
		private static int MUL_UN8(int a, int b)
 | 
			
		||||
		{
 | 
			
		||||
		    var t = (a * b) + 0x80;
 | 
			
		||||
		    return (((t >> 8) + t) >> 8);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@ -5,6 +5,12 @@ namespace Strawberry
 | 
			
		||||
{
 | 
			
		||||
	static public class Calc
 | 
			
		||||
	{
 | 
			
		||||
		[Inline]
 | 
			
		||||
		static public bool BitCheck(int bits, int pos)
 | 
			
		||||
		{
 | 
			
		||||
		    return (bits & (1 << pos)) != 0;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		//Move toward a target value without crossing it
 | 
			
		||||
		[Inline]
 | 
			
		||||
		static public float Approach(float value, float target, float maxDelta)
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user