mirror of
https://github.com/MaddyThorson/StrawberryBF.git
synced 2024-12-01 17:38:56 +08:00
Some aseprite loading work. Still need to solve decompression
This commit is contained in:
parent
524bb14919
commit
aed77272ac
|
@ -1,8 +1,7 @@
|
||||||
FileVersion = 1
|
FileVersion = 1
|
||||||
Dependencies = {corlib = "*", SDL2 = "*"}
|
Dependencies = {corlib = "*", SDL2 = "*", MiniZ = "*"}
|
||||||
|
|
||||||
[Project]
|
[Project]
|
||||||
Name = "Strawberry"
|
Name = "Strawberry"
|
||||||
TargetType = "BeefLib"
|
TargetType = "BeefLib"
|
||||||
StartupObject = "Program"
|
StartupObject = "Program"
|
||||||
DefaultNamespace = "Strawberry"
|
|
||||||
|
|
|
@ -15,6 +15,7 @@ namespace Strawberry
|
||||||
|
|
||||||
public class Game
|
public class Game
|
||||||
{
|
{
|
||||||
|
public readonly Dictionary<String, Sprite> Sprites;
|
||||||
public readonly List<VirtualInput> VirtualInputs;
|
public readonly List<VirtualInput> VirtualInputs;
|
||||||
public readonly String Title;
|
public readonly String Title;
|
||||||
public readonly int Width;
|
public readonly int Width;
|
||||||
|
@ -42,7 +43,6 @@ namespace Strawberry
|
||||||
: base()
|
: base()
|
||||||
{
|
{
|
||||||
Game = this;
|
Game = this;
|
||||||
VirtualInputs = new List<VirtualInput>();
|
|
||||||
|
|
||||||
Title = windowTitle;
|
Title = windowTitle;
|
||||||
Width = width;
|
Width = width;
|
||||||
|
@ -81,8 +81,13 @@ namespace Strawberry
|
||||||
SDLMixer.OpenAudio(44100, SDLMixer.MIX_DEFAULT_FORMAT, 2, 4096);
|
SDLMixer.OpenAudio(44100, SDLMixer.MIX_DEFAULT_FORMAT, 2, 4096);
|
||||||
SDLTTF.Init();
|
SDLTTF.Init();
|
||||||
|
|
||||||
BuildTypeLists();
|
VirtualInputs = new List<VirtualInput>();
|
||||||
Input.[Friend]Init(gamepadLimit);
|
Input.[Friend]Init(gamepadLimit);
|
||||||
|
|
||||||
|
Sprites = new Dictionary<String, Sprite>();
|
||||||
|
//LoadSprites();
|
||||||
|
|
||||||
|
BuildTypeLists();
|
||||||
}
|
}
|
||||||
|
|
||||||
public ~this()
|
public ~this()
|
||||||
|
@ -104,6 +109,8 @@ namespace Strawberry
|
||||||
|
|
||||||
DisposeTypeLists();
|
DisposeTypeLists();
|
||||||
Input.[Friend]Dispose();
|
Input.[Friend]Dispose();
|
||||||
|
DisposeSprites();
|
||||||
|
Sprite.[Friend]Dispose();
|
||||||
Game = null;
|
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.SetRenderDrawColor(Renderer, ClearColor.R, ClearColor.G, ClearColor.B, ClearColor.A);
|
||||||
SDL.RenderClear(Renderer);
|
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
|
// Type assignable caching
|
||||||
|
|
||||||
private void BuildTypeLists()
|
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
|
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
|
//Move toward a target value without crossing it
|
||||||
[Inline]
|
[Inline]
|
||||||
static public float Approach(float value, float target, float maxDelta)
|
static public float Approach(float value, float target, float maxDelta)
|
||||||
|
|
Loading…
Reference in New Issue
Block a user