///|
/// Error categories used by the JSON parser, binary parser, validator,
/// runtime builder, exporter, and CLI report model.
pub(all) enum PixelAnimErrorKind {
ErrorNone
ErrorJsonParse
ErrorExpectedObject
ErrorMissingField
ErrorInvalidField
ErrorInvalidFrame
ErrorInvalidTag
ErrorInvalidSlice
ErrorInvalidLayer
ErrorNotAseprite
ErrorUnsupportedChunk
ErrorChunkOutOfBounds
ErrorTruncatedData
ErrorUnsupportedCompression
ErrorRuntimeState
ErrorInvalidArgument
} derive(Eq, @debug.Debug)
///|
pub fn PixelAnimErrorKind::code(self : PixelAnimErrorKind) -> String {
match self {
ErrorNone => "none"
ErrorJsonParse => "json-parse"
ErrorExpectedObject => "expected-object"
ErrorMissingField => "missing-field"
ErrorInvalidField => "invalid-field"
ErrorInvalidFrame => "invalid-frame"
ErrorInvalidTag => "invalid-tag"
ErrorInvalidSlice => "invalid-slice"
ErrorInvalidLayer => "invalid-layer"
ErrorNotAseprite => "not-aseprite"
ErrorUnsupportedChunk => "unsupported-chunk"
ErrorChunkOutOfBounds => "chunk-out-of-bounds"
ErrorTruncatedData => "truncated-data"
ErrorUnsupportedCompression => "unsupported-compression"
ErrorRuntimeState => "runtime-state"
ErrorInvalidArgument => "invalid-argument"
}
}
///|
pub(all) struct PixelAnimError {
kind : PixelAnimErrorKind
path : String
offset : Int
message : String
} derive(Eq, @debug.Debug)
///|
pub fn PixelAnimError::none() -> PixelAnimError {
{ kind: ErrorNone, path: "$", offset: 0, message: "" }
}
///|
pub fn PixelAnimError::new(
kind : PixelAnimErrorKind,
message : String,
path? : String = "$",
offset? : Int = 0,
) -> PixelAnimError {
{ kind, path, offset, message }
}
///|
pub fn PixelAnimError::code(self : PixelAnimError) -> String {
self.kind.code()
}
///|
pub fn PixelAnimError::is_error(self : PixelAnimError) -> Bool {
self.kind != ErrorNone
}
///|
pub(all) struct Point {
x : Int
y : Int
} derive(Eq, @debug.Debug)
///|
pub fn Point::new(x : Int, y : Int) -> Point {
{ x, y }
}
///|
pub(all) struct Size {
width : Int
height : Int
} derive(Eq, @debug.Debug)
///|
pub fn Size::new(width : Int, height : Int) -> Size {
{ width, height }
}
///|
pub fn Size::empty() -> Size {
{ width: 0, height: 0 }
}
///|
pub fn Size::area(self : Size) -> Int {
self.width * self.height
}
///|
pub fn Size::is_positive(self : Size) -> Bool {
self.width > 0 && self.height > 0
}
///|
pub(all) struct Rect {
x : Int
y : Int
width : Int
height : Int
} derive(Eq, @debug.Debug)
///|
pub fn Rect::new(x : Int, y : Int, width : Int, height : Int) -> Rect {
{ x, y, width, height }
}
///|
pub fn Rect::empty() -> Rect {
{ x: 0, y: 0, width: 0, height: 0 }
}
///|
pub fn Rect::right(self : Rect) -> Int {
self.x + self.width
}
///|
pub fn Rect::bottom(self : Rect) -> Int {
self.y + self.height
}
///|
pub fn Rect::area(self : Rect) -> Int {
self.width * self.height
}
///|
pub fn Rect::is_positive(self : Rect) -> Bool {
self.width > 0 && self.height > 0
}
///|
pub fn Rect::contains_point(self : Rect, point : Point) -> Bool {
point.x >= self.x &&
point.y >= self.y &&
point.x < self.right() &&
point.y < self.bottom()
}
///|
pub fn Rect::contains_rect(self : Rect, other : Rect) -> Bool {
other.x >= self.x &&
other.y >= self.y &&
other.right() <= self.right() &&
other.bottom() <= self.bottom()
}
///|
pub fn Rect::intersects(self : Rect, other : Rect) -> Bool {
self.x < other.right() &&
self.right() > other.x &&
self.y < other.bottom() &&
self.bottom() > other.y
}
///|
pub(all) enum TagDirection {
DirectionForward
DirectionReverse
DirectionPingPong
DirectionPingPongReverse
DirectionUnknown(String)
} derive(Eq, @debug.Debug)
///|
pub fn TagDirection::from_string(value : String) -> TagDirection {
match value {
"forward" | "Forward" => DirectionForward
"reverse" | "Reverse" => DirectionReverse
"pingpong" | "ping-pong" | "PingPong" => DirectionPingPong
"pingpong_reverse" | "ping-pong-reverse" | "PingPongReverse" =>
DirectionPingPongReverse
_ => DirectionUnknown(value)
}
}
///|
pub fn TagDirection::to_string(self : TagDirection) -> String {
match self {
DirectionForward => "forward"
DirectionReverse => "reverse"
DirectionPingPong => "pingpong"
DirectionPingPongReverse => "pingpong_reverse"
DirectionUnknown(value) => value
}
}
///|
pub fn TagDirection::is_pingpong(self : TagDirection) -> Bool {
match self {
DirectionPingPong | DirectionPingPongReverse => true
_ => false
}
}
///|
pub(all) enum LoopMode {
LoopOnce
LoopRepeat
LoopPingPong
} derive(Eq, @debug.Debug)
///|
pub fn LoopMode::to_string(self : LoopMode) -> String {
match self {
LoopOnce => "once"
LoopRepeat => "repeat"
LoopPingPong => "pingpong"
}
}
///|
pub(all) struct FrameInfo {
name : String
index : Int
rect : Rect
rotated : Bool
trimmed : Bool
sprite_source_size : Rect
source_size : Size
duration_ms : Int
} derive(Eq, @debug.Debug)
///|
pub fn FrameInfo::new(
name : String,
index : Int,
rect : Rect,
duration_ms : Int,
rotated? : Bool = false,
trimmed? : Bool = false,
sprite_source_size? : Rect = Rect::empty(),
source_size? : Size = Size::empty(),
) -> FrameInfo {
{
name,
index,
rect,
rotated,
trimmed,
sprite_source_size,
source_size,
duration_ms,
}
}
///|
pub fn FrameInfo::is_valid(self : FrameInfo) -> Bool {
self.index >= 0 && self.rect.is_positive() && self.duration_ms > 0
}
///|
pub fn FrameInfo::duration_seconds(self : FrameInfo) -> Double {
self.duration_ms.to_double() / 1000.0
}
///|
pub(all) struct FrameTag {
name : String
from_index : Int
to_index : Int
direction : TagDirection
color : String
user_data : String
} derive(Eq, @debug.Debug)
///|
pub fn FrameTag::new(
name : String,
from_index : Int,
to_index : Int,
direction? : TagDirection = DirectionForward,
color? : String = "",
user_data? : String = "",
) -> FrameTag {
{ name, from_index, to_index, direction, color, user_data }
}
///|
pub fn FrameTag::frame_count(self : FrameTag) -> Int {
if self.to_index >= self.from_index {
self.to_index - self.from_index + 1
} else {
0
}
}
///|
pub fn FrameTag::contains_index(self : FrameTag, index : Int) -> Bool {
index >= self.from_index && index <= self.to_index
}
///|
pub(all) struct SliceKey {
frame : Int
bounds : Rect
center : Rect
pivot : Point
has_center : Bool
has_pivot : Bool
} derive(Eq, @debug.Debug)
///|
pub fn SliceKey::new(
frame : Int,
bounds : Rect,
center? : Rect = Rect::empty(),
pivot? : Point = Point::new(0, 0),
has_center? : Bool = false,
has_pivot? : Bool = false,
) -> SliceKey {
{ frame, bounds, center, pivot, has_center, has_pivot }
}
///|
pub fn SliceKey::is_valid(self : SliceKey) -> Bool {
self.frame >= 0 && self.bounds.is_positive()
}
///|
pub(all) struct SliceInfo {
name : String
color : String
keys : Array[SliceKey]
} derive(Eq, @debug.Debug)
///|
pub fn SliceInfo::new(
name : String,
keys : Array[SliceKey],
color? : String = "",
) -> SliceInfo {
{ name, color, keys }
}
///|
pub fn SliceInfo::key_for_frame(
self : SliceInfo,
frame_index : Int,
) -> SliceKey? {
let mut best : SliceKey? = None
for key in self.keys {
if key.frame <= frame_index {
best = Some(key)
}
}
best
}
///|
pub fn SliceInfo::has_keys(self : SliceInfo) -> Bool {
self.keys.length() > 0
}
///|
pub(all) struct LayerInfo {
name : String
opacity : Int
blend_mode : String
visible : Bool
} derive(Eq, @debug.Debug)
///|
pub fn LayerInfo::new(
name : String,
opacity? : Int = 255,
blend_mode? : String = "normal",
visible? : Bool = true,
) -> LayerInfo {
{ name, opacity, blend_mode, visible }
}
///|
pub fn LayerInfo::is_visible(self : LayerInfo) -> Bool {
self.visible && self.opacity > 0
}
///|
pub(all) struct SpriteSheetMeta {
app : String
version : String
image : String
format : String
scale : String
size : Size
frame_tags : Array[FrameTag]
layers : Array[LayerInfo]
slices : Array[SliceInfo]
} derive(Eq, @debug.Debug)
///|
pub fn SpriteSheetMeta::empty() -> SpriteSheetMeta {
{
app: "",
version: "",
image: "",
format: "",
scale: "1",
size: Size::empty(),
frame_tags: [],
layers: [],
slices: [],
}
}
///|
pub(all) struct SpriteSheet {
frames : Array[FrameInfo]
meta : SpriteSheetMeta
source_length : Int
} derive(Eq, @debug.Debug)
///|
pub fn SpriteSheet::empty() -> SpriteSheet {
{ frames: [], meta: SpriteSheetMeta::empty(), source_length: 0 }
}
///|
pub fn SpriteSheet::frame_count(self : SpriteSheet) -> Int {
self.frames.length()
}
///|
pub fn SpriteSheet::duration_ms(self : SpriteSheet) -> Int {
for frame in self.frames; total = 0 {
continue total + frame.duration_ms
} nobreak {
total
}
}
///|
pub fn SpriteSheet::duration_seconds(self : SpriteSheet) -> Double {
self.duration_ms().to_double() / 1000.0
}
///|
pub fn SpriteSheet::find_frame(self : SpriteSheet, name : String) -> FrameInfo? {
for frame in self.frames {
if frame.name == name {
return Some(frame)
}
}
None
}
///|
pub fn SpriteSheet::frame_at(self : SpriteSheet, index : Int) -> FrameInfo? {
if index >= 0 && index < self.frames.length() {
Some(self.frames[index])
} else {
None
}
}
///|
pub fn SpriteSheet::find_tag(self : SpriteSheet, name : String) -> FrameTag? {
for tag in self.meta.frame_tags {
if tag.name == name {
return Some(tag)
}
}
None
}
///|
pub(all) struct PixelAnimParseResult {
ok : Bool
sheet : SpriteSheet
error : PixelAnimError
} derive(Eq, @debug.Debug)
///|
pub fn PixelAnimParseResult::success(
sheet : SpriteSheet,
) -> PixelAnimParseResult {
{ ok: true, sheet, error: PixelAnimError::none() }
}
///|
pub fn PixelAnimParseResult::failure(
error : PixelAnimError,
) -> PixelAnimParseResult {
{ ok: false, sheet: SpriteSheet::empty(), error }
}
///|
pub(all) struct AseHeader {
file_size : Int
frame_count : Int
width : Int
height : Int
color_depth : Int
flags : Int
speed_ms : Int
transparent_index : Int
color_count : Int
pixel_width : Int
pixel_height : Int
} derive(Eq, @debug.Debug)
///|
pub fn AseHeader::empty() -> AseHeader {
{
file_size: 0,
frame_count: 0,
width: 0,
height: 0,
color_depth: 0,
flags: 0,
speed_ms: 0,
transparent_index: 0,
color_count: 0,
pixel_width: 1,
pixel_height: 1,
}
}
///|
pub fn AseHeader::canvas_size(self : AseHeader) -> Size {
Size::new(self.width, self.height)
}
///|
pub(all) struct AseChunk {
frame_index : Int
offset : Int
size : Int
kind : Int
kind_name : String
name : String
layer_index : Int
cel_type : Int
opacity : Int
child_level : Int
extra : String
} derive(Eq, @debug.Debug)
///|
pub fn AseChunk::new(
frame_index : Int,
offset : Int,
size : Int,
kind : Int,
kind_name : String,
name? : String = "",
layer_index? : Int = -1,
cel_type? : Int = -1,
opacity? : Int = 255,
child_level? : Int = 0,
extra? : String = "",
) -> AseChunk {
{
frame_index,
offset,
size,
kind,
kind_name,
name,
layer_index,
cel_type,
opacity,
child_level,
extra,
}
}
///|
pub fn AseChunk::end_offset(self : AseChunk) -> Int {
self.offset + self.size
}
///|
pub(all) struct AseFrame {
index : Int
offset : Int
size : Int
duration_ms : Int
chunk_count : Int
chunks : Array[AseChunk]
} derive(Eq, @debug.Debug)
///|
pub fn AseFrame::new(
index : Int,
offset : Int,
size : Int,
duration_ms : Int,
chunk_count : Int,
chunks : Array[AseChunk],
) -> AseFrame {
{ index, offset, size, duration_ms, chunk_count, chunks }
}
///|
pub(all) struct AseFile {
header : AseHeader
frames : Array[AseFrame]
source_length : Int
} derive(Eq, @debug.Debug)
///|
pub fn AseFile::empty() -> AseFile {
{ header: AseHeader::empty(), frames: [], source_length: 0 }
}
///|
pub fn AseFile::chunk_count(self : AseFile) -> Int {
for frame in self.frames; total = 0 {
continue total + frame.chunks.length()
} nobreak {
total
}
}
///|
pub(all) struct AseParseResult {
ok : Bool
file : AseFile
error : PixelAnimError
} derive(Eq, @debug.Debug)
///|
pub fn AseParseResult::success(file : AseFile) -> AseParseResult {
{ ok: true, file, error: PixelAnimError::none() }
}
///|
pub fn AseParseResult::failure(error : PixelAnimError) -> AseParseResult {
{ ok: false, file: AseFile::empty(), error }
}