1use rusqlite::Connection;
12use rusqlite::types::Value;
13
14use crate::clip::ClipFile;
15use crate::container::ContainerWriter;
16use crate::database;
17use crate::edit::{NewLayer, new_uuid};
18use crate::error::Result;
19use crate::image::RgbaImage;
20
21const SCHEMA: &str = include_str!("data/new_file_schema.sql");
22const PARAM_SCHEME: &str = include_str!("data/param_scheme.tsv");
23const ELEM_SCHEME: &str = include_str!("data/elem_scheme.tsv");
24
25const EXTERNAL_COLUMNS: [(&str, &str); 10] = [
27 ("Offscreen", "BlockData"),
28 ("VectorObjectList", "VectorData"),
29 ("Canvas3DModelBank", "BankData"),
30 ("Canvas3DModelLoader", "ModelData"),
31 ("Track", "TrackActionMixer"),
32 ("Track", "TrackActionMixer2"),
33 ("CanvasItemBinary", "ItemBinaryData"),
34 ("Manager3DOd", "SceneData"),
35 ("ModelData3D", "Layer3DModelData"),
36 ("TimeLapseBlob", "BlobData"),
37];
38
39#[derive(Debug, Clone, PartialEq)]
41pub struct NewCanvas {
42 pub width: u32,
44 pub height: u32,
46 pub resolution: f64,
48 pub paper: Option<[u8; 3]>,
50 pub layer_name: String,
52}
53
54impl Default for NewCanvas {
55 fn default() -> Self {
56 NewCanvas {
57 width: 1024,
58 height: 1024,
59 resolution: 350.0,
60 paper: Some([255; 3]),
61 layer_name: "レイヤー 1".to_owned(),
62 }
63 }
64}
65
66const CANVAS_ID: i64 = 1;
68const ITEM_BANK_ID: i64 = 1;
69const CUT_BANK_ID: i64 = 2;
70const LAYER_COMP_MANAGER_ID: i64 = 3;
71const ROOT_ID: i64 = 2;
72
73pub fn preview_size(width: u32, height: u32) -> (u32, u32) {
76 let (mut w, mut h) = (width.max(1), height.max(1));
77 while w.max(h) > 2048 {
78 (w, h) = ((w / 2).max(1), (h / 2).max(1));
79 }
80 (w, h)
81}
82
83impl ClipFile {
84 pub fn create(canvas: &NewCanvas) -> Result<ClipFile> {
89 let connection = Connection::open_in_memory()?;
90 connection.execute_batch(SCHEMA)?;
91 fill_registry(&connection, PARAM_SCHEME, ELEM_SCHEME)?;
92 for (table, column) in EXTERNAL_COLUMNS {
93 connection.execute("INSERT INTO ExternalTableAndColumnName VALUES (?1, ?2)", (table, column))?;
94 }
95 fill_singletons(&connection, canvas)?;
96 let bytes = ContainerWriter::new(new_uuid()).assemble(&database::serialize(&connection)?)?;
97 let mut file = ClipFile::from_bytes(bytes)?;
100 let root = ROOT_ID;
101 file.database().execute("DELETE FROM Layer WHERE MainId = ?1", [root])?;
102 file.insert_layer_row(root, NewLayer::Root, "")?;
103 file.reload()?;
104 if let Some(color) = canvas.paper {
105 file.add_layer(root, NewLayer::Paper(color), "用紙")?;
106 }
107 file.add_layer(root, NewLayer::Raster, &canvas.layer_name)?;
108 file.update_preview()?;
109 Ok(file)
110 }
111}
112
113pub(crate) fn fill_registry(connection: &Connection, param_scheme: &str, elem_scheme: &str) -> Result<()> {
115 let mut insert = connection.prepare(
116 "INSERT INTO ParamScheme (TableName, LabelName, DataType, Flag, OwnerType, LockType, LockSpecified, \
117 AlternativeLockType, AlternativeLockSpecified, LinkTable) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10)",
118 )?;
119 for line in param_scheme.lines().filter(|line| !line.is_empty()) {
120 let cells: Vec<Value> = line
121 .split('\t')
122 .enumerate()
123 .map(|(i, cell)| match (i, cell) {
124 (_, "\\N") => Value::Null,
125 (0 | 1 | 9, text) => Value::Text(text.to_owned()),
126 (_, number) => number.parse().map(Value::Integer).unwrap_or(Value::Null),
127 })
128 .collect();
129 insert.execute(rusqlite::params_from_iter(cells))?;
130 }
131 for line in elem_scheme.lines().filter(|line| !line.is_empty()) {
132 let (table, kind) = line.split_once('\t').unwrap_or((line, "0"));
133 connection.execute(
134 "INSERT INTO ElemScheme (TableName, ElemType, MaxIndex) VALUES (?1, ?2, 0)",
135 (table, kind.parse::<i64>().unwrap_or(0)),
136 )?;
137 }
138 Ok(())
139}
140
141pub(crate) fn comic_story() -> Vec<(&'static str, Value)> {
144 let (int, text, real) = (Value::Integer, |s: &str| Value::Text(s.to_owned()), Value::Real);
145 vec![
146 ("ComicStoryStoryName", text("")),
147 ("ComicStoryUseStoryIndex", int(0)),
148 ("ComicStoryStoryIndex", int(1)),
149 ("ComicStorySubTitle", text("")),
150 ("ComicStoryStoryPosition", int(1)),
151 ("ComicStoryAuthorName", text("")),
152 ("ComicStoryAuthorPosition", int(2)),
153 ("ComicStoryUsePageNumber", int(0)),
154 ("ComicStoryPageNumberStart", int(1)),
155 ("ComicStoryPageNumberPosition", int(3)),
156 ("ComicStoryNombreStart", int(1)),
157 ("ComicStoryNombreFont", text("")),
158 ("ComicStoryNombreFontSize", real(9.0)),
159 ("ComicStoryNombreFontPostScriptName", text("")),
160 ("ComicStoryUseShownNombre", int(0)),
161 ("ComicStoryNombrePosition", int(5)),
162 ("ComicStoryNombrePrefix", text("")),
163 ("ComicStoryNombreSuffix", text("")),
164 ("ComicStoryUseHiddenNombre", int(0)),
165 ("ComicStoryNombreColor", int(0)),
166 ("ComicStoryNombreUseEdge", int(0)),
167 ("ComicStoryNombreEdgeWidth", real(0.5)),
168 ("ComicStoryNombreEdgeUnit", int(2)),
169 ("ComicStoryYPageNombreOffset", real(5.0)),
170 ("ComicStoryYPageNombreOffsetUnit", int(2)),
171 ("ComicStoryXPageNombreOffset", real(0.0)),
172 ("ComicStoryXPageNombreOffsetUnit", int(2)),
173 ]
174}
175
176pub(crate) fn insert(connection: &Connection, table: &str, values: &[(&str, Value)]) -> Result<()> {
177 let names: Vec<String> = values.iter().map(|(name, _)| database::quote(name)).collect();
178 let marks: Vec<String> = (1..=values.len()).map(|i| format!("?{i}")).collect();
179 let sql = format!("INSERT INTO {} ({}) VALUES ({})", database::quote(table), names.join(", "), marks.join(", "));
180 connection.execute(&sql, rusqlite::params_from_iter(values.iter().map(|(_, value)| value.clone())))?;
181 Ok(())
182}
183
184fn fill_singletons(connection: &Connection, canvas: &NewCanvas) -> Result<()> {
185 let (int, text, real) = (Value::Integer, |s: &str| Value::Text(s.to_owned()), Value::Real);
186 let (width, height) = (canvas.width.max(1) as f64, canvas.height.max(1) as f64);
187 let paper = canvas.paper.unwrap_or([255; 3]).map(|c| int(c as i64 * 0x0101_0101));
188 let mut project = vec![
189 ("ProjectInternalVersion", text("1.1.0")),
190 ("ProjectName", text("")),
191 ("ProjectCanvas", int(CANVAS_ID)),
192 ("ProjectItemBank", int(ITEM_BANK_ID)),
193 ("ProjectCutBank", int(CUT_BANK_ID)),
194 ("ProjectLayerCompManager", int(LAYER_COMP_MANAGER_ID)),
195 ("ProjectRootCanvasNode", int(0)),
196 ];
197 project.extend(comic_story());
198 project.extend([
199 ("DefaultPageUnit", int(0)),
200 ("DefaultPageWidth", real(width)),
201 ("DefaultPageHeight", real(height)),
202 ("DefaultPageResolution", real(canvas.resolution)),
203 ("DefaultPageChannelBytes", int(1)),
204 ("DefaultPageChannelOrder", int(33)),
205 ("DefaultPageColorType", int(0)),
206 ("DefaultPageBlackChecked", int(1)),
207 ("DefaultPageWhiteChecked", int(1)),
208 ("DefaultPageToneLine", real(60.0)),
209 ("DefaultPageUsePaper", int(canvas.paper.is_some() as i64)),
210 ("DefaultPagePaperRed", paper[0].clone()),
211 ("DefaultPagePaperGreen", paper[1].clone()),
212 ("DefaultPagePaperBlue", paper[2].clone()),
213 ("DefaultPageUseTemplate", int(0)),
214 ("DefaultPageTemplatePath", text("")),
215 ("DefaultPageTemplatePath2", text("")),
216 ("DefaultPageTemplateName", text("")),
217 ("DefaultPageIsUserTemplate", int(0)),
218 ("DefaultPageDoublePage", int(0)),
219 ("DefaultPagePresetID", text("")),
220 ("DefaultPageCheckBookBinding", int(0)),
221 ("DefaultPageCelUseTemplate", int(0)),
222 ("DefaultPageCelTemplatePath", text("")),
223 ("DefaultPageCelTemplatePath2", text("")),
224 ("DefaultPageCelTemplateName", text("")),
225 ("DefaultPageCelTemplateUUID", text("")),
226 ("DefaultPageCelIsUserTemplate", int(0)),
227 ("DefaultPageUseCropFrame", int(0)),
228 ("DefaultPageTemplateUUID", text("")),
229 ("DefaultPageUseDefaultCoverInfo", int(0)),
230 ("DefaultPageSettingType", text("Illust")),
231 ("DefaultPageRecordTimeLapse", int(0)),
232 ("DefaultPageTimeLapseCodec", int(0)),
233 ]);
234 insert(connection, "Project", &project)?;
235 let mut canvas_row = vec![
236 ("MainId", int(CANVAS_ID)),
237 ("CanvasUnit", int(0)),
238 ("CanvasWidth", real(width)),
239 ("CanvasHeight", real(height)),
240 ("CanvasResolution", real(canvas.resolution)),
241 ("CanvasChannelBytes", int(1)),
242 ("CanvasDefaultChannelOrder", int(33)),
243 ("CanvasRootFolder", int(ROOT_ID)),
244 ("CanvasCurrentLayer", int(0)),
245 ("CanvasDoSimulateColor", int(0)),
246 ("CanvasSimulateRenderingIntent", int(1)),
247 ("CanvasSimulateUseLibraryType", int(2)),
248 ("CanvasUseColorAdjustment", int(0)),
249 ("CanvasDefaultColorTypeIndex", int(0)),
250 ("CanvasDefaultColorBlackChecked", int(1)),
251 ("CanvasDefaultColorWhiteChecked", int(1)),
252 ("CanvasDefaultToneLine", real(60.0)),
253 ("CanvasDoublePage", int(0)),
254 ("CanvasRenderMipmapForceSaved", int(0)),
255 ("CanvasWorkTime", int(0)),
256 ("ComicBindPosition", int(1)),
257 ("ComicHasComicCover", int(0)),
258 ("ComicHasDoubleCover", int(0)),
259 ("ComicPageIndex", int(0)),
260 ("ComicIsLeftPage", int(1)),
261 ];
262 canvas_row.extend(comic_story());
263 canvas_row.push(("TimeLapseCodec", int(0)));
264 insert(connection, "Canvas", &canvas_row)?;
265 insert(
266 connection,
267 "CanvasItemBank",
268 &[("MainId", int(ITEM_BANK_ID)), ("ModelBankMainIndex", int(0)), ("BankRootItemMainIndex", int(0))],
269 )?;
270 insert(
271 connection,
272 "AnimationCutBank",
273 &[
274 ("MainId", int(CUT_BANK_ID)),
275 ("FirstTimeLine", int(0)),
276 ("FirstScenario", int(0)),
277 ("CurrentIndex", int(0)),
278 ("Enable", int(0)),
279 ],
280 )?;
281 insert(
282 connection,
283 "LayerCompManager",
284 &[
285 ("MainId", int(LAYER_COMP_MANAGER_ID)),
286 ("FirstLayerCompIndex", int(0)),
287 ("LastStateLayerCompIndex", int(0)),
288 ("AppliedLayerCompIndex", int(-1)),
289 ],
290 )?;
291 let (preview_width, preview_height) = preview_size(canvas.width, canvas.height);
292 let preview = RgbaImage::new(preview_width, preview_height).to_png()?;
293 insert(
294 connection,
295 "CanvasPreview",
296 &[
297 ("MainId", int(1)),
298 ("CanvasId", int(CANVAS_ID)),
299 ("ImageType", int(1)),
300 ("ImageWidth", int(preview_width as i64)),
301 ("ImageHeight", int(preview_height as i64)),
302 ("ImageData", Value::Blob(preview)),
303 ],
304 )?;
305 insert(
306 connection,
307 "Layer",
308 &[
309 ("MainId", int(ROOT_ID)),
310 ("CanvasId", int(CANVAS_ID)),
311 ("LayerType", int(crate::model::LAYER_TYPE_ROOT)),
312 ("LayerFolder", int(1)),
313 ("LayerFirstChildIndex", int(0)),
314 ("LayerNextIndex", int(0)),
315 ],
316 )?;
317 for (table, max) in [("Canvas", CANVAS_ID), ("Project", 1), ("Layer", ROOT_ID)] {
318 connection.execute("UPDATE ElemScheme SET MaxIndex = ?1 WHERE TableName = ?2", (max, table))?;
319 }
320 Ok(())
321}
322
323#[cfg(test)]
324mod tests {
325 use super::*;
326 use crate::model::LayerKind;
327 use crate::verify::{CheckOptions, check_clip};
328
329 #[test]
330 fn new_files_pass_the_self_check_and_read_back() {
331 let canvas = NewCanvas { width: 300, height: 200, ..Default::default() };
332 let mut file = ClipFile::create(&canvas).unwrap();
333 let mut image = RgbaImage::new(40, 30);
334 image.fill([200, 10, 20, 255]);
335 let raster = file.layers.flatten().iter().find(|(l, _)| l.kind == LayerKind::Raster).unwrap().0.main_id;
336 file.set_layer_pixels(raster, &image, (10, 20)).unwrap();
337 file.update_preview().unwrap();
338 let bytes = file.to_bytes().unwrap();
339 let report = check_clip(bytes.clone(), CheckOptions::default());
340 assert!(report.ok(), "{:?}", serde_json::to_string(&report).unwrap());
341 let again = ClipFile::from_bytes(bytes).unwrap();
342 assert_eq!((again.canvas.width_px, again.canvas.height_px), (300, 200));
343 let kinds: Vec<LayerKind> = again.layers.flatten().iter().map(|(l, _)| l.kind).collect();
344 assert_eq!(kinds, [LayerKind::Root, LayerKind::Paper, LayerKind::Raster]);
345 let (pixels, origin) = again.layer_pixels(again.layers.get(raster).unwrap()).unwrap().unwrap();
346 assert_eq!(pixels.pixel(15 - origin.0 as u32, 25 - origin.1 as u32), [200, 10, 20, 255]);
347 let preview = again.preview().unwrap().unwrap();
348 assert_eq!(preview.pixel(15, 25), [200, 10, 20, 255]);
349 assert_eq!(preview.pixel(100, 100), [255, 255, 255, 255]);
350 let thumbnail = again.layer_thumbnail(again.layers.get(raster).unwrap()).unwrap().unwrap();
352 assert_eq!((thumbnail.width, thumbnail.height, thumbnail.pixel(5, 5)[3]), (512, 512, 0));
353 }
354
355 #[test]
356 fn empty_raster_layers_store_empty_blocks_like_csp() {
357 let file = ClipFile::create(&NewCanvas { width: 600, height: 400, ..Default::default() }).unwrap();
361 let file = ClipFile::from_bytes(file.to_bytes().unwrap()).unwrap();
362 for (layer, _) in file.layers.flatten() {
363 let mipmap = file.mipmap(layer.render_mipmap).unwrap().unwrap();
364 for (level, info) in mipmap.levels.iter().enumerate() {
365 let offscreen = file.offscreen(info.offscreen).unwrap().unwrap();
366 let stored = layer.kind == LayerKind::Raster && level == 0;
367 assert_eq!(offscreen.data.is_some(), stored, "{:?} level {level}", layer.kind);
368 if let Some(data) = &offscreen.data {
369 assert_eq!(data.blocks.len(), 6);
370 assert!(data.record_sizes().iter().all(|&size| size == crate::offscreen::EMPTY_RECORD_SIZE));
371 assert_eq!(offscreen.attribute.block_sizes().unwrap(), &data.record_sizes()[..]);
372 }
373 }
374 }
375 }
376
377 #[test]
378 fn layer_thumbnails_are_read_through_their_offscreen() {
379 let mut file = ClipFile::create(&NewCanvas { width: 64, height: 48, ..Default::default() }).unwrap();
380 let mut image = RgbaImage::new(8, 6);
381 image.fill([0, 90, 200, 255]);
382 let raster = file.layers.flatten().iter().find(|(l, _)| l.kind == LayerKind::Raster).unwrap().0.main_id;
383 file.set_layer_pixels(raster, &image, (0, 0)).unwrap();
384 let layer = file.layers.get(raster).unwrap().clone();
386 let base = file.mipmap(layer.render_mipmap).unwrap().unwrap().levels[0].offscreen;
387 file.database()
388 .execute(
389 "UPDATE LayerThumbnail SET ThumbnailOffscreen = ?1 WHERE MainId = ?2",
390 [base, layer.row.int("LayerRenderThumbnail")],
391 )
392 .unwrap();
393 let thumbnail = file.layer_thumbnail(&layer).unwrap().unwrap();
394 assert_eq!(thumbnail.pixel(1, 1), [0, 90, 200, 255]);
395 }
396}