1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
//! Read, verify and write CLIP STUDIO PAINT files (.clip / .cmc).
//!
//! The format is not published; everything here was learnt from real files
//! and is written down in the repository's `docs/spec/`. Input files are
//! never modified: files are read into memory, and changes are written only
//! by an explicit save. This crate is not made or endorsed by CELSYS.
//!
//! # Reading
//!
//! [`ClipFile`] opens a .clip: the canvas settings, the layer tree, every
//! table of the embedded SQLite database, and the pixels, vectors and texts
//! of the layers. [`CmcFile`] opens a .cmc (a multi-page work) and lists its
//! pages in reading order.
//!
//! ```no_run
//! use csp_format::{ClipFile, CmcFile};
//!
//! let file = ClipFile::open("page0001.clip")?;
//! for (layer, depth) in file.layers.flatten() {
//! println!("{}{} ({})", " ".repeat(depth), layer.name, layer.kind.name());
//! if let Some((image, (x, y))) = file.layer_pixels(layer)? {
//! println!(" {}x{} px at ({x}, {y})", image.width, image.height);
//! }
//! }
//!
//! let work = CmcFile::open("work.cmc")?;
//! for path in work.page_paths(std::path::Path::new("."))? {
//! println!("{}", path.display());
//! }
//! # Ok::<(), csp_format::Error>(())
//! ```
//!
//! # Writing
//!
//! A file saved without changes is byte-identical to the input. Changes go
//! through [`ClipFile`]'s methods ([`edit`]) or straight into its database;
//! new canvases and works are made with [`NewCanvas`] and [`NewWork`].
//!
//! ```
//! use csp_format::{ClipFile, LayerKind, NewCanvas, RgbaImage};
//!
//! let mut file = ClipFile::create(&NewCanvas { width: 640, height: 480, ..NewCanvas::default() })?;
//! let mut image = RgbaImage::new(64, 64);
//! image.fill([200, 30, 30, 255]);
//! let id = file.add_raster_layer("red square", &image, (10, 20))?;
//! file.set_layer_opacity(id, 0.5)?;
//!
//! let reopened = ClipFile::from_bytes(file.to_bytes()?)?;
//! let layer = reopened.layers.get(id).unwrap();
//! assert_eq!(layer.kind, LayerKind::Raster);
//! let (pixels, (x, y)) = reopened.layer_pixels(layer)?.unwrap();
//! assert_eq!(pixels.pixel((10 - x) as u32, (20 - y) as u32), [200, 30, 30, 255]);
//! assert_eq!(pixels.pixel((9 - x) as u32, (20 - y) as u32)[3], 0);
//! # Ok::<(), csp_format::Error>(())
//! ```
//!
//! # Rendering and checking
//!
//! [`render::render`] composites the layers (blend modes, clipping, masks,
//! effects, corrections, gradients, vectors) into an image, and
//! [`verify::check_clip`] runs every self-check (round trips of each part,
//! references between tables and chunks).
//!
//! # Modules
//!
//! | Area | Modules |
//! |---|---|
//! | Files | [`clip`], [`cmc`], [`container`], [`database`], [`schema`], [`version`] |
//! | Layers | [`model`], [`offscreen`], [`vector`], [`text`], [`gradient`], [`correction`], [`effect`], [`tone`], [`material`] |
//! | Animation | [`animation`], [`cmt`] |
//! | Writing | [`create`], [`edit`], [`copy`], [`work`], [`cmc_pages`], [`blank`], [`story`], [`vector_build`] |
//! | Output | [`render`], [`verify`], [`image`] |
//!
//! Items hidden from these docs are used by the project's investigation
//! tools and are not part of the stable API.
/// The SQLite binding this crate's database API uses (`rusqlite`), so that
/// callers use the same version.
pub use rusqlite;
pub use ClipFile;
pub use CmcFile;
pub use NewCanvas;
pub use ;
pub use ;
pub use ;
pub use VersionInfo;
pub use NewWork;