1use std::collections::{HashMap, HashSet};
21
22use rusqlite::types::Value;
23use rusqlite::{Connection, OptionalExtension, params_from_iter};
24
25use crate::clip::ClipFile;
26use crate::database::{Row, has_column, has_table, quote, select, value_as_text};
27use crate::edit::new_external_id;
28use crate::error::{Error, Result};
29use crate::model::{LAYER_TYPE_MASKED, LayerKind};
30use crate::offscreen::{Attribute, Parameter, PixelKind};
31
32pub const OWNER_CANVAS: i64 = 2;
34pub const OWNER_LAYER: i64 = 4;
36const CHAIN_COLUMNS: [(&str, &str); 2] = [("MipmapInfo", "NextIndex"), ("BrushStyle", "NextIndex")];
38pub const LAYER_TABLES: [&str; 9] = [
45 "Layer",
46 "VectorObjectList",
47 "BrushStyleManager",
48 "BrushStyle",
49 "BrushEffectorGraphData",
50 "BrushPatternImage",
51 "BrushPatternStyle",
52 "BrushFixedSpray",
53 "FillStyle",
54];
55pub const LAYER_CANVAS_COLUMNS: [&str; 2] = ["BrushStyleManager", "BrushStyleReadProtect070"];
57pub const SHARED_TABLES: [&str; 7] = [
60 "BrushStyleManager",
61 "BrushStyle",
62 "BrushEffectorGraphData",
63 "BrushPatternImage",
64 "BrushPatternStyle",
65 "BrushFixedSpray",
66 "FillStyle",
67];
68pub const DROPPED_LAYER_LINKS: [&str; 13] = [
73 "LayerLayerMaskMipmap",
74 "LayerLayerMaskThumbnail",
75 "ResizableOriginalMipmap",
76 "ComicFrameLineMipmap",
77 "FirstLayerObject",
78 "Manager3D",
79 "Manager3DOd",
80 "SpecialRulerManager",
81 "StreamLineIndex",
82 "VoiceSpeakParam",
83 "RulerVectorIndex",
84 "TimeLineOriginalMipmap",
85 "TimeLineOriginalMaskMipmap",
86];
87const MASK_PARAMETERS: [u32; 12] = [1, 1, 0, 1, 0, 0, 0, 1, 256, 0, 256, 256];
90
91pub type PayloadHook<'a> = dyn Fn(&str, &str, Vec<u8>) -> Result<Vec<u8>> + 'a;
93pub type RowHook<'a> = dyn FnMut(&mut Row) -> Result<()> + 'a;
95
96#[derive(Debug, Clone, PartialEq, Eq)]
98pub struct ColumnInfo {
99 pub link_table: Option<String>,
101 pub owner_type: i64,
103}
104
105fn table_columns(database: &Connection, table: &str) -> Result<Vec<String>> {
106 Ok(crate::database::columns(database, table)?.into_iter().map(|(name, _)| name).collect())
107}
108
109pub fn column_infos(database: &Connection, table: &str) -> Result<HashMap<String, ColumnInfo>> {
111 let mut infos = HashMap::new();
112 if has_table(database, "ParamScheme")? {
113 for row in
114 select(database, "SELECT LabelName, LinkTable, OwnerType FROM ParamScheme WHERE TableName = ?1", [table])?
115 {
116 let label = row.text("LabelName").unwrap_or_default();
117 let mut link_table = row.text("LinkTable").filter(|link| !link.is_empty() && link != "0");
118 if CHAIN_COLUMNS.contains(&(table, label.as_str())) {
119 link_table = Some(table.to_owned());
120 }
121 infos.insert(label, ColumnInfo { link_table, owner_type: row.int("OwnerType") });
122 }
123 }
124 for (chain_table, label) in CHAIN_COLUMNS {
125 if chain_table == table {
126 infos.entry(label.to_owned()).or_insert(ColumnInfo { link_table: Some(table.to_owned()), owner_type: 0 });
127 }
128 }
129 Ok(infos)
130}
131
132pub fn external_columns(database: &Connection) -> Result<HashSet<(String, String)>> {
134 if !has_table(database, "ExternalTableAndColumnName")? {
135 return Ok(HashSet::new());
136 }
137 Ok(select(database, "SELECT TableName, ColumnName FROM ExternalTableAndColumnName", [])?
138 .iter()
139 .map(|row| (row.text("TableName").unwrap_or_default(), row.text("ColumnName").unwrap_or_default()))
140 .collect())
141}
142
143pub fn insert_row(database: &Connection, table: &str, row: &Row) -> Result<()> {
145 let known: HashSet<String> = table_columns(database, table)?.into_iter().collect();
146 let kept: Vec<(&str, &Value)> = row.iter().filter(|(column, _)| known.contains(*column)).collect();
147 let names: Vec<String> = kept.iter().map(|(column, _)| quote(column)).collect();
148 let marks: Vec<String> = (1..=kept.len()).map(|i| format!("?{i}")).collect();
149 database.execute(
150 &format!("INSERT INTO {} ({}) VALUES ({})", quote(table), names.join(", "), marks.join(", ")),
151 params_from_iter(kept.iter().map(|(_, value)| *value)),
152 )?;
153 Ok(())
154}
155
156fn with_value(row: Row, column: &str, value: Value) -> Row {
158 let mut row = row;
159 if !row.set(column, value.clone()) {
160 let mut columns = row.columns().to_vec();
161 let mut values = row.values().to_vec();
162 columns.push(column.to_owned());
163 values.push(value);
164 row = Row::new(columns, values);
165 }
166 row
167}
168
169fn allocate(database: &Connection, table: &str) -> Result<i64> {
171 crate::schema::allocate_main_id(database, table)
172}
173
174pub struct RowCopier<'a> {
177 source: &'a ClipFile,
178 payload_hook: Option<&'a PayloadHook<'a>>,
179 canvas_id: i64,
181 pub owner_layer: i64,
183 pub skip: HashSet<(String, String)>,
185 done: HashMap<(String, i64), i64>,
186 externals: HashSet<(String, String)>,
187 infos: HashMap<String, HashMap<String, ColumnInfo>>,
188 pub new_externals: Vec<(String, Vec<u8>)>,
190}
191
192impl<'a> RowCopier<'a> {
193 pub fn new(source: &'a ClipFile, payload_hook: Option<&'a PayloadHook<'a>>, canvas_id: i64) -> Result<Self> {
195 Ok(RowCopier {
196 source,
197 payload_hook,
198 canvas_id,
199 owner_layer: 0,
200 skip: HashSet::new(),
201 done: HashMap::new(),
202 externals: external_columns(source.database())?,
203 infos: HashMap::new(),
204 new_externals: Vec::new(),
205 })
206 }
207
208 pub fn copy(&mut self, target: &Connection, table: &str, main_id: i64) -> Result<i64> {
210 let key = (table.to_owned(), main_id);
211 if let Some(id) = self.done.get(&key) {
212 return Ok(*id);
213 }
214 let row = self
215 .source
216 .row(table, main_id)?
217 .ok_or_else(|| Error::format(format!("{table} {main_id} not found in the source")))?;
218 let new_id = allocate(target, table)?;
219 self.done.insert(key, new_id);
220 let row = self.translate(target, table, row, new_id)?;
221 insert_row(target, table, &row)?;
222 Ok(new_id)
223 }
224
225 pub fn translate(&mut self, target: &Connection, table: &str, row: Row, new_id: i64) -> Result<Row> {
227 if !self.infos.contains_key(table) {
228 self.infos.insert(table.to_owned(), column_infos(self.source.database(), table)?);
229 }
230 let mut columns = Vec::with_capacity(row.columns().len());
231 let mut values = Vec::with_capacity(row.columns().len());
232 for (column, value) in row.iter() {
233 if column == "_PW_ID" {
234 continue;
235 }
236 let info = self.infos[table].get(column).cloned();
237 let key = (table.to_owned(), column.to_owned());
238 let value = if column == "MainId" {
239 Value::Integer(new_id)
240 } else if self.skip.contains(&key) {
241 value.clone()
242 } else if info.as_ref().is_some_and(|info| info.owner_type == OWNER_CANVAS) {
243 Value::Integer(self.canvas_id)
244 } else if info.as_ref().is_some_and(|info| info.owner_type == OWNER_LAYER) {
245 Value::Integer(self.owner_layer)
246 } else if let (Some(link), Value::Integer(id)) = (info.and_then(|info| info.link_table), value)
247 && *id != 0
248 {
249 Value::Integer(self.copy(target, &link, *id)?)
250 } else if let Value::Blob(_) | Value::Text(_) = value
251 && self.externals.contains(&key)
252 && let Some(id) = value_as_text(value).filter(|id| !id.is_empty())
253 {
254 Value::Blob(self.copy_external(table, column, &id)?.into_bytes())
255 } else {
256 value.clone()
257 };
258 columns.push(column.to_owned());
259 values.push(value);
260 }
261 Ok(Row::new(columns, values))
262 }
263
264 fn copy_external(&mut self, table: &str, column: &str, external_id: &str) -> Result<String> {
265 let new_id = new_external_id();
266 if let Some(payload) = self.source.external(external_id) {
267 let mut payload = payload.to_vec();
268 if let Some(hook) = self.payload_hook {
269 payload = hook(table, column, payload)?;
270 }
271 self.new_externals.push((new_id.clone(), payload));
272 }
273 Ok(new_id)
274 }
275}
276
277pub fn merge_schema(target: &Connection, source: &Connection, tables: &[&str]) -> Result<()> {
280 for table in tables {
281 if !has_table(source, table)? {
282 continue;
283 }
284 if !has_table(target, table)? {
285 let sql: String = source.query_row(
286 "SELECT sql FROM sqlite_master WHERE type = 'table' AND name = ?1",
287 [table],
288 |row| row.get(0),
289 )?;
290 target.execute(&sql, [])?;
291 copy_scheme_rows(target, source, table, None)?;
292 ensure_elem_scheme(target, source, table)?;
293 continue;
294 }
295 merge_columns(target, source, table, None)?;
296 }
297 Ok(())
298}
299
300pub fn merge_columns(target: &Connection, source: &Connection, table: &str, only: Option<&[&str]>) -> Result<()> {
303 if !has_table(source, table)? || !has_table(target, table)? {
304 return Ok(());
305 }
306 let have: HashSet<String> = table_columns(target, table)?.into_iter().collect();
307 let mut statement = source.prepare(&format!("PRAGMA table_info({})", quote(table)))?;
308 let columns: Vec<(String, String, Option<String>)> = statement
309 .query_map([], |row| Ok((row.get(1)?, row.get(2)?, row.get(4)?)))?
310 .collect::<std::result::Result<_, _>>()?;
311 for (name, declared_type, default) in columns {
312 if have.contains(&name) || only.is_some_and(|only| !only.contains(&name.as_str())) {
313 continue;
314 }
315 let mut definition = format!("{} {declared_type}", quote(&name));
316 if let Some(default) = default {
317 definition += &format!(" DEFAULT {default}");
318 }
319 target.execute(&format!("ALTER TABLE {} ADD COLUMN {definition}", quote(table)), [])?;
320 copy_scheme_rows(target, source, table, Some(&name))?;
321 }
322 Ok(())
323}
324
325fn copy_scheme_rows(target: &Connection, source: &Connection, table: &str, column: Option<&str>) -> Result<()> {
326 if !has_table(target, "ParamScheme")? || !has_table(source, "ParamScheme")? {
327 return Ok(());
328 }
329 let target_columns: HashSet<String> = table_columns(target, "ParamScheme")?.into_iter().collect();
330 let common: Vec<String> = table_columns(source, "ParamScheme")?
331 .into_iter()
332 .filter(|name| name != "_PW_ID" && target_columns.contains(name))
333 .collect();
334 if !common.iter().any(|name| name == "LabelName") {
335 return Ok(());
336 }
337 let quoted: Vec<String> = common.iter().map(|name| quote(name)).collect();
338 let mut sql = format!("SELECT {} FROM ParamScheme WHERE TableName = ?1", quoted.join(", "));
339 let mut parameters = vec![table.to_owned()];
340 if let Some(column) = column {
341 sql += " AND LabelName = ?2";
342 parameters.push(column.to_owned());
343 }
344 let present: HashSet<String> = select(target, "SELECT LabelName FROM ParamScheme WHERE TableName = ?1", [table])?
345 .iter()
346 .filter_map(|row| row.text("LabelName"))
347 .collect();
348 let marks: Vec<String> = (1..=common.len()).map(|i| format!("?{i}")).collect();
349 let insert = format!("INSERT INTO ParamScheme ({}) VALUES ({})", quoted.join(", "), marks.join(", "));
350 for row in select(source, &sql, params_from_iter(parameters.iter()))? {
351 if row.text("LabelName").is_some_and(|label| present.contains(&label)) {
352 continue;
353 }
354 target.execute(&insert, params_from_iter(row.values().iter()))?;
355 }
356 Ok(())
357}
358
359fn ensure_elem_scheme(target: &Connection, source: &Connection, table: &str) -> Result<()> {
360 if !has_table(target, "ElemScheme")? {
361 return Ok(());
362 }
363 let query = "SELECT ElemType FROM ElemScheme WHERE TableName = ?1";
364 if target.query_row(query, [table], |_| Ok(())).optional()?.is_some() {
365 return Ok(());
366 }
367 let element_type: i64 = if has_table(source, "ElemScheme")? {
368 source.query_row(query, [table], |row| row.get::<_, Option<i64>>(0)).optional()?.flatten().unwrap_or(0)
369 } else {
370 0
371 };
372 target
373 .execute("INSERT INTO ElemScheme (TableName, ElemType, MaxIndex) VALUES (?1, ?2, 0)", (table, element_type))?;
374 Ok(())
375}
376
377pub fn share_rows(target: &Connection, source: &Connection, tables: &[&str]) -> Result<Vec<String>> {
384 let mut copied = Vec::new();
385 for table in tables {
386 if !has_table(source, table)? || !has_table(target, table)? {
387 continue;
388 }
389 let count: i64 = target.query_row(&format!("SELECT COUNT(*) FROM {}", quote(table)), [], |row| row.get(0))?;
390 if count > 0 {
391 continue;
392 }
393 for row in select(source, &format!("SELECT * FROM {}", quote(table)), [])? {
394 let kept: Vec<String> = row.columns().iter().filter(|c| *c != "_PW_ID").cloned().collect();
395 let values: Vec<Value> = kept.iter().map(|c| row.get(c).cloned().unwrap_or(Value::Null)).collect();
396 insert_row(target, table, &Row::new(kept, values))?;
397 }
398 copied.push((*table).to_owned());
399 if has_table(source, "ElemScheme")? && has_table(target, "ElemScheme")? {
400 let source_max: Option<i64> = source
401 .query_row("SELECT MaxIndex FROM ElemScheme WHERE TableName = ?1", [table], |row| row.get(0))
402 .optional()?
403 .flatten();
404 if let Some(source_max) = source_max {
405 target.execute(
406 "UPDATE ElemScheme SET MaxIndex = MAX(MaxIndex, ?1) WHERE TableName = ?2",
407 (source_max, table),
408 )?;
409 }
410 }
411 }
412 if has_column(target, "Canvas", "BrushStyleManager")? && has_column(source, "Canvas", "BrushStyleManager")? {
413 let manager: Value = source.query_row("SELECT BrushStyleManager FROM Canvas", [], |row| row.get(0))?;
414 let current: Value = target.query_row("SELECT BrushStyleManager FROM Canvas", [], |row| row.get(0))?;
415 if matches!(current, Value::Null | Value::Integer(0)) {
416 target.execute("UPDATE Canvas SET BrushStyleManager = ?1", [manager])?;
417 }
418 }
419 let flag = "BrushStyleReadProtect070";
420 if copied.iter().any(|table| table == "BrushStyle")
421 && has_column(target, "Canvas", flag)?
422 && has_column(source, "Canvas", flag)?
423 {
424 let value: Value = source.query_row(&format!("SELECT {flag} FROM Canvas"), [], |row| row.get(0))?;
425 target.execute(&format!("UPDATE Canvas SET {flag} = ?1"), [value])?;
426 }
427 Ok(copied)
428}
429
430#[derive(Default)]
432pub struct CopyLayer<'h> {
433 pub payload_hook: Option<&'h PayloadHook<'h>>,
436 pub row_hook: Option<&'h mut RowHook<'h>>,
438 pub parent: Option<i64>,
441}
442
443impl ClipFile {
444 pub fn prepare_copy_from(&mut self, source: &ClipFile) -> Result<()> {
447 merge_schema(self.database(), source.database(), &LAYER_TABLES)?;
448 merge_columns(self.database(), source.database(), "Canvas", Some(&LAYER_CANVAS_COLUMNS))?;
449 let copied = share_rows(self.database(), source.database(), &SHARED_TABLES)?;
450 if copied.iter().any(|table| table == "BrushPatternImage") {
451 self.copy_pattern_images(source)?;
452 }
453 Ok(())
454 }
455
456 fn copy_pattern_images(&mut self, source: &ClipFile) -> Result<()> {
461 let images = select(source.database(), "SELECT MainId, Mipmap FROM BrushPatternImage", [])?;
462 let mut copier = RowCopier::new(source, None, self.canvas.main_id)?;
463 let mut moved = Vec::new();
464 for image in images {
465 let mipmap = image.int("Mipmap");
466 if mipmap != 0 {
467 moved.push((image.int("MainId"), copier.copy(self.database(), "Mipmap", mipmap)?));
468 }
469 }
470 for (id, payload) in copier.new_externals {
471 self.set_external(&id, payload);
472 }
473 for (image, mipmap) in moved {
474 self.database().execute("UPDATE BrushPatternImage SET Mipmap = ?1 WHERE MainId = ?2", (mipmap, image))?;
475 }
476 Ok(())
477 }
478
479 pub fn copy_layer_from(&mut self, source: &ClipFile, layer_id: i64, options: CopyLayer<'_>) -> Result<i64> {
491 let layer = source
492 .layers
493 .get(layer_id)
494 .ok_or_else(|| Error::format(format!("No layer {layer_id} in the source")))?
495 .clone();
496 if layer.kind == LayerKind::Root {
497 return Err(Error::format("The root folder cannot be copied"));
498 }
499 let mut row = layer.row.clone();
500 for column in DROPPED_LAYER_LINKS {
501 row.set(column, Value::Null);
502 }
503 let new_id = self.next_main_id("Layer")?;
504 let (translated, copied) = {
505 let mut copier = RowCopier::new(source, options.payload_hook, self.canvas.main_id)?;
506 copier.owner_layer = new_id;
507 for column in ["LayerRenderMipmap", "LayerRenderThumbnail", "LayerNextIndex", "LayerFirstChildIndex"] {
508 copier.skip.insert(("Layer".to_owned(), column.to_owned()));
509 }
510 let translated = copier.translate(self.database(), "Layer", row, new_id)?;
511 (translated, copier.new_externals)
512 };
513 for (id, payload) in copied {
514 self.set_external(&id, payload);
515 }
516 let raster = layer.layer_type & crate::model::LAYER_TYPE_RASTER != 0;
517 let stored = if raster { source.mipmap_base(layer.render_mipmap)? } else { None };
521 let (mipmap, render_offset) = match &stored {
522 Some(base) => (
523 self.new_mipmap_sized(new_id, (base.parameter.width, base.parameter.height), None, true)?,
524 layer.render_offset,
525 ),
526 None => (self.new_mipmap(new_id, None, raster)?, (-layer.offset.0, -layer.offset.1)),
527 };
528 let thumbnail = self.new_thumbnail(new_id)?;
529 let int = Value::Integer;
530 let mut row = translated;
531 let layer_type = row.int("LayerType") & !LAYER_TYPE_MASKED;
533 for (column, value) in [
534 ("LayerType", int(layer_type)),
535 ("LayerRenderMipmap", int(mipmap)),
536 ("LayerRenderThumbnail", int(thumbnail)),
537 ("LayerNextIndex", int(0)),
538 ("LayerFirstChildIndex", int(0)),
539 ("LayerRenderOffscrOffsetX", int(render_offset.0)),
540 ("LayerRenderOffscrOffsetY", int(render_offset.1)),
541 ("LayerMaskOffsetX", int(0)),
542 ("LayerMaskOffsetY", int(0)),
543 ("LayerMaskOffscrOffsetX", int(0)),
544 ("LayerMaskOffscrOffsetY", int(0)),
545 ("LayerSelect", int(0)),
546 ("LayerUuid", Value::Text(crate::edit::layer_uuid())),
547 ] {
548 row = with_value(row, column, value);
549 }
550 let visibility = row.int("LayerVisibility");
551 if visibility & 2 != 0 {
552 row = with_value(row, "LayerVisibility", int(visibility & !2));
553 }
554 if let Some(hook) = options.row_hook {
555 hook(&mut row)?;
556 }
557 insert_row(self.database(), "Layer", &row)?;
558 let parent = options.parent.unwrap_or(self.canvas.root_folder);
559 self.link_on_top(parent, new_id)?;
560 self.reload()?;
561 if raster && let Some((image, origin)) = source.layer_pixels(&layer)? {
562 self.set_layer_pixels(new_id, &image, origin)?;
563 }
564 Ok(new_id)
565 }
566
567 pub fn add_frame_images(&mut self, folder_id: i64) -> Result<()> {
572 let mask = self.new_mipmap(folder_id, None, false)?;
573 let mask_thumbnail = self.new_thumbnail(folder_id)?;
574 let line = self.new_mipmap(folder_id, None, false)?;
575 let levels = self.mipmap(mask)?.map(|mipmap| mipmap.levels).unwrap_or_default();
576 for level in levels {
577 let row = self
578 .row("Offscreen", level.offscreen)?
579 .ok_or_else(|| Error::format(format!("No Offscreen {}", level.offscreen)))?;
580 let parameter = Attribute::parse(row.blob("Attribute").unwrap_or_default())?.parameter()?;
581 self.database().execute(
582 "UPDATE Offscreen SET Attribute = ?1 WHERE MainId = ?2",
583 (mask_attribute(parameter.width, parameter.height), level.offscreen),
584 )?;
585 }
586 let int = Value::Integer;
587 for (column, value) in [
588 ("LayerLayerMaskMipmap", int(mask)),
589 ("LayerLayerMaskThumbnail", int(mask_thumbnail)),
590 ("ComicFrameLineMipmap", int(line)),
591 ] {
592 self.set_layer_value(folder_id, column, value)?;
593 }
594 self.reload()
595 }
596}
597
598fn mask_attribute(width: u32, height: u32) -> Vec<u8> {
600 let mut attribute = Attribute::new(&Parameter::new(PixelKind::Color, width, height), None);
601 if let Some(section) = attribute.sections.iter_mut().find(|section| section.name == "Parameter") {
602 section.values[4..16].copy_from_slice(&MASK_PARAMETERS);
603 }
604 attribute.to_bytes()
605}
606
607pub fn brush_style_in(target: &Connection, id: u32) -> Result<u32> {
610 if !has_table(target, "BrushStyle")? {
611 return Ok(id);
612 }
613 if target.query_row("SELECT 1 FROM BrushStyle WHERE MainId = ?1", [id], |_| Ok(())).optional()?.is_some() {
614 return Ok(id);
615 }
616 let first: Option<i64> = target.query_row("SELECT MIN(MainId) FROM BrushStyle", [], |row| row.get(0))?;
617 Ok(first.map_or(id, |first| first as u32))
618}
619
620#[cfg(test)]
621mod tests {
622 use super::*;
623 use crate::create::NewCanvas;
624 use crate::edit::NewLayer;
625 use crate::image::RgbaImage;
626
627 #[test]
628 fn raster_layers_copy_with_their_pixels() {
629 let mut source = ClipFile::create(&NewCanvas::default()).unwrap();
630 let mut image = RgbaImage::new(40, 30);
631 image.fill([10, 200, 30, 255]);
632 let id = source.add_raster_layer("source", &image, (100, 50)).unwrap();
633 source.set_layer_opacity(id, 0.5).unwrap();
634 let source = ClipFile::from_bytes(source.to_bytes().unwrap()).unwrap();
635
636 let mut target = ClipFile::create(&NewCanvas::default()).unwrap();
637 let folder = target.add_layer(target.canvas.root_folder, NewLayer::Folder, "folder").unwrap();
638 target.prepare_copy_from(&source).unwrap();
639 let mut rename = |row: &mut Row| {
640 row.set("LayerName", Value::Text("copy".into()));
641 Ok(())
642 };
643 let options = CopyLayer { row_hook: Some(&mut rename), parent: Some(folder), ..Default::default() };
644 let copy = target.copy_layer_from(&source, id, options).unwrap();
645 let target = ClipFile::from_bytes(target.to_bytes().unwrap()).unwrap();
646 let layer = target.layers.get(copy).unwrap();
647 assert_eq!(layer.name, "copy");
648 assert_eq!(layer.kind, LayerKind::Raster);
649 assert_eq!(target.layers.get(folder).unwrap().first_child, copy);
650 assert!((layer.opacity_fraction() - 0.5).abs() < 0.01);
651 assert_ne!(layer.row.text("LayerUuid"), source.layers.get(id).unwrap().row.text("LayerUuid"));
652 let part = target.layer_pixels_in_box(layer, (100, 50, 140, 80)).unwrap();
653 assert_eq!(part, image);
654 assert_eq!(target.layer_pixels_in_box(layer, (99, 49, 100, 50)).unwrap().pixel(0, 0), [0; 4]);
655 let report = crate::verify::check_clip(target.to_bytes().unwrap(), Default::default());
656 assert!(report.ok(), "{:?}", report.errors);
657 }
658
659 #[test]
660 fn dropped_links_become_null() {
661 let mut source = ClipFile::create(&NewCanvas::default()).unwrap();
662 let layer = source.add_raster_layer("one", &RgbaImage::new(4, 4), (0, 0)).unwrap();
663 source
664 .database()
665 .execute_batch(
666 "ALTER TABLE Layer ADD COLUMN ComicFrameLineMipmap INTEGER; UPDATE Layer SET ComicFrameLineMipmap = 0;",
667 )
668 .unwrap();
669 let source = ClipFile::from_bytes(source.to_bytes().unwrap()).unwrap();
670 let mut target = ClipFile::create(&NewCanvas::default()).unwrap();
671 target.prepare_copy_from(&source).unwrap();
672 let copy = target.copy_layer_from(&source, layer, CopyLayer::default()).unwrap();
673 let value: Value = target
674 .database()
675 .query_row("SELECT ComicFrameLineMipmap FROM Layer WHERE MainId = ?1", [copy], |row| row.get(0))
676 .unwrap();
677 assert_eq!(value, Value::Null);
678 }
679
680 #[test]
681 fn brush_styles_come_with_the_textures_they_use() {
682 let mut source = ClipFile::create(&NewCanvas::default()).unwrap();
684 let mut texture = RgbaImage::new(8, 8);
685 texture.fill([90, 90, 90, 255]);
686 let layer = source.add_raster_layer("texture", &texture, (0, 0)).unwrap();
687 let texture_mipmap = source.layers.get(layer).unwrap().render_mipmap;
688 source
689 .database()
690 .execute_batch(
691 "CREATE TABLE BrushStyleManager(_PW_ID INTEGER PRIMARY KEY AUTOINCREMENT, MainId INTEGER, CanvasId INTEGER, FirstBrushStyle INTEGER, FirstPatternImage INTEGER);
692 CREATE TABLE BrushStyle(_PW_ID INTEGER PRIMARY KEY AUTOINCREMENT, MainId INTEGER, NextIndex INTEGER, TexturePattern INTEGER);
693 CREATE TABLE BrushPatternImage(_PW_ID INTEGER PRIMARY KEY AUTOINCREMENT, MainId INTEGER, NextIndex INTEGER, ImageData BLOB);
694 INSERT INTO BrushStyleManager(MainId, CanvasId, FirstBrushStyle, FirstPatternImage) VALUES (1, 1, 1, 1);
695 INSERT INTO BrushStyle(MainId, NextIndex, TexturePattern) VALUES (1, 2, 0), (2, 0, 1);
696 INSERT INTO BrushPatternImage(MainId, NextIndex, ImageData) VALUES (1, 0, x'0102');",
697 )
698 .unwrap();
699 source.database().execute("ALTER TABLE BrushPatternImage ADD COLUMN Mipmap INTEGER", []).unwrap();
700 source.database().execute("UPDATE BrushPatternImage SET Mipmap = ?1", [texture_mipmap]).unwrap();
701 source
702 .database()
703 .execute(
704 "INSERT INTO ParamScheme (TableName, LabelName, DataType, OwnerType, LinkTable) VALUES ('BrushPatternImage', 'Mipmap', 1, 0, 'Mipmap')",
705 [],
706 )
707 .unwrap();
708 source
709 .database()
710 .execute_batch("ALTER TABLE Canvas ADD COLUMN BrushStyleReadProtect070 INTEGER; UPDATE Canvas SET BrushStyleReadProtect070 = 1;")
711 .unwrap();
712 let source = ClipFile::from_bytes(source.to_bytes().unwrap()).unwrap();
713 let mut target = ClipFile::create(&NewCanvas::default()).unwrap();
714 target.add_raster_layer("other", &RgbaImage::new(4, 4), (0, 0)).unwrap();
715 target.prepare_copy_from(&source).unwrap();
716 let target = ClipFile::from_bytes(target.to_bytes().unwrap()).unwrap();
717 let images = select(target.database(), "SELECT MainId, ImageData, Mipmap FROM BrushPatternImage", []).unwrap();
718 assert_eq!(images.len(), 1);
719 assert_eq!(images[0].int("MainId"), 1);
720 assert_eq!(images[0].blob("ImageData"), Some(&[1u8, 2][..]));
721 let mipmap = images[0].int("Mipmap");
723 assert!(target.layers.flatten().iter().all(|(layer, _)| layer.render_mipmap != mipmap));
724 let base = target.mipmap_base(mipmap).unwrap().unwrap();
725 let original = source.mipmap_base(texture_mipmap).unwrap().unwrap();
726 assert_eq!(base.parameter, original.parameter);
727 let levels = |file: &ClipFile, id| file.mipmap(id).unwrap().unwrap().levels.len();
728 assert_eq!(levels(&target, mipmap), levels(&source, texture_mipmap));
729 let styles = select(target.database(), "SELECT MainId FROM BrushStyle ORDER BY MainId", []).unwrap();
730 assert_eq!(styles.iter().map(|row| row.int("MainId")).collect::<Vec<_>>(), [1, 2]);
731 let flag: i64 =
732 target.database().query_row("SELECT BrushStyleReadProtect070 FROM Canvas", [], |row| row.get(0)).unwrap();
733 assert_eq!(flag, 1);
734 }
735}