file.rs 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
  1. use crate::data::Mapping;
  2. use crate::errors::{ThymeFileError, ThymeFileStructureError};
  3. use crate::image::{Image, Pixel};
  4. use serde::{Deserialize, Serialize};
  5. use std::collections::HashMap;
  6. use std::io::{Read, Seek, Write};
  7. const CURRENT_VERSION: &str = "0";
  8. #[derive(Debug, Clone, Copy)]
  9. pub enum StitchType {
  10. Normal,
  11. HalfUp,
  12. HalfDown,
  13. }
  14. impl StitchType {
  15. fn to_u8(self) -> u8 {
  16. match self {
  17. StitchType::Normal => b'x',
  18. StitchType::HalfUp => b'/',
  19. StitchType::HalfDown => b'\\',
  20. }
  21. }
  22. }
  23. #[derive(Debug, Clone)]
  24. pub struct Stitch {
  25. pub color: ColorIdx,
  26. pub typ: StitchType,
  27. }
  28. #[derive(Debug, Serialize, Deserialize)]
  29. pub struct Color {
  30. pub name: String,
  31. pub symbol: String,
  32. pub color: Pixel,
  33. }
  34. #[derive(Debug, Clone, Copy)]
  35. pub struct ColorIdx {
  36. pub idx: u32,
  37. }
  38. #[derive(Debug)]
  39. pub struct ThymeFile {
  40. pub width: u32,
  41. pub height: u32,
  42. pub palette: Vec<Color>,
  43. pub payload: Vec<Option<Stitch>>,
  44. pub metadata: HashMap<String, String>,
  45. }
  46. impl ThymeFile {
  47. pub fn from_image_and_config(image: &Image, Mapping(mapping): &Mapping) -> ThymeFile {
  48. let width = image.width;
  49. let height = image.height;
  50. let mut palette = Vec::new();
  51. let mut lookup = HashMap::new();
  52. let mut next_idx = 0;
  53. for (color, info) in mapping.iter() {
  54. if let Some(i) = info {
  55. let idx = ColorIdx { idx: next_idx };
  56. next_idx += 1;
  57. palette.push(Color {
  58. color: *color,
  59. name: i.name.to_owned(),
  60. symbol: i.symbol.to_owned(),
  61. });
  62. lookup.insert(color, idx);
  63. }
  64. }
  65. let payload = image
  66. .iter()
  67. .map(|(_idx, pixel)| {
  68. lookup.get(&pixel).map(|color| Stitch {
  69. color: *color,
  70. typ: StitchType::Normal,
  71. })
  72. })
  73. .collect();
  74. let metadata = HashMap::new();
  75. ThymeFile {
  76. width,
  77. height,
  78. palette,
  79. payload,
  80. metadata,
  81. }
  82. }
  83. pub fn blank(width: u32, height: u32) -> ThymeFile {
  84. let palette = Vec::new();
  85. let mut payload = Vec::with_capacity((width * height) as usize);
  86. for _ in 0..(width * height) {
  87. payload.push(None);
  88. }
  89. let metadata = HashMap::new();
  90. ThymeFile {
  91. width,
  92. height,
  93. palette,
  94. payload,
  95. metadata,
  96. }
  97. }
  98. pub fn from_stream<R>(stream: &mut R) -> Result<ThymeFile, ThymeFileError>
  99. where
  100. R: Read + Seek,
  101. {
  102. fn missing_component(component: &str) -> ThymeFileError {
  103. ThymeFileStructureError::new(format!(
  104. "Missing component in thyme file: `{}`",
  105. component
  106. ))
  107. .into()
  108. }
  109. let mut zip = zip::ZipArchive::new(stream)?;
  110. let mut width = None;
  111. let mut height = None;
  112. let mut metadata = None;
  113. let mut palette = None;
  114. let mut payload = None;
  115. for i in 0..zip.len() {
  116. let file = zip.by_index(i)?;
  117. match file.name() {
  118. "THYME_VERSION" => {
  119. // TODO: actually check this
  120. }
  121. "dimensions.json" => {
  122. let (w, h): (u32, u32) = serde_json::from_reader(file)?;
  123. width = Some(w);
  124. height = Some(h);
  125. }
  126. "metadata.json" => metadata = Some(serde_json::from_reader(file)?),
  127. "palette.json" => palette = Some(serde_json::from_reader(file)?),
  128. "payload.json" => {
  129. let ps: Vec<Option<IntermediateStitch>> = serde_json::from_reader(file)?;
  130. let ps: Result<Vec<Option<Stitch>>, ThymeFileError> = ps
  131. .iter()
  132. .map(|n| n.map(IntermediateStitch::to_stitch).transpose())
  133. .collect();
  134. payload = Some(ps?);
  135. }
  136. name => {
  137. return Err(ThymeFileStructureError::new(format!(
  138. "Unrecognized element: {}",
  139. name
  140. ))
  141. .into())
  142. }
  143. }
  144. // whatever
  145. }
  146. Ok(ThymeFile {
  147. width: width.ok_or_else(|| missing_component("dimensions"))?,
  148. height: height.ok_or_else(|| missing_component("dimensions"))?,
  149. palette: palette.ok_or_else(|| missing_component("palette"))?,
  150. payload: payload.ok_or_else(|| missing_component("payload"))?,
  151. metadata: metadata.ok_or_else(|| missing_component("metadata"))?,
  152. })
  153. }
  154. pub fn to_stream<W>(&self, stream: &mut W) -> std::io::Result<()>
  155. where
  156. W: Write + Seek,
  157. {
  158. let mut zip = zip::ZipWriter::new(stream);
  159. let options =
  160. zip::write::FileOptions::default().compression_method(zip::CompressionMethod::Bzip2);
  161. // add version metadata
  162. zip.start_file("THYME_VERSION", options)?;
  163. writeln!(zip, "{}", CURRENT_VERSION)?;
  164. // add dimensions
  165. zip.start_file("dimensions.json", options)?;
  166. serde_json::to_writer(&mut zip, &(self.width, self.height))?;
  167. // add metadata
  168. zip.start_file("metadata.json", options)?;
  169. serde_json::to_writer(&mut zip, &self.metadata)?;
  170. // add palette
  171. zip.start_file("palette.json", options)?;
  172. serde_json::to_writer(&mut zip, &self.palette)?;
  173. // add image payload
  174. zip.start_file("payload.json", options)?;
  175. serde_json::to_writer(&mut zip, &self.json_stitches())?;
  176. // add version
  177. zip.finish()?;
  178. Ok(())
  179. }
  180. fn json_stitches(&self) -> Vec<Option<IntermediateStitch>> {
  181. self.payload
  182. .iter()
  183. .map(|stitch| {
  184. stitch
  185. .as_ref()
  186. .map(|s| IntermediateStitch(s.typ.to_u8(), s.color.idx))
  187. })
  188. .collect()
  189. }
  190. pub fn iter(&self) -> ThymeImageIterator<'_> {
  191. ThymeImageIterator {
  192. palette: &self.palette,
  193. x: 0,
  194. y: 0,
  195. width: self.width,
  196. values: self.payload.iter(),
  197. }
  198. }
  199. }
  200. // internal structs for serializing/deserializing the image part
  201. #[derive(Debug, Clone, Copy, Serialize, Deserialize)]
  202. struct IntermediateStitch(u8, u32);
  203. impl IntermediateStitch {
  204. fn to_stitch(self) -> Result<Stitch, ThymeFileError> {
  205. let typ = match self.0 {
  206. b'x' => StitchType::Normal,
  207. b'/' => StitchType::HalfUp,
  208. b'\\' => StitchType::HalfDown,
  209. _ => {
  210. return Err(ThymeFileStructureError::new(format!(
  211. "Unknown stitch type: {}",
  212. self.0
  213. ))
  214. .into())
  215. }
  216. };
  217. Ok(Stitch {
  218. typ,
  219. color: ColorIdx { idx: self.1 },
  220. })
  221. }
  222. }
  223. pub struct ThymeImageIterator<'a> {
  224. width: u32,
  225. x: u32,
  226. y: u32,
  227. palette: &'a [Color],
  228. values: std::slice::Iter<'a, Option<Stitch>>,
  229. }
  230. impl<'a> Iterator for ThymeImageIterator<'a> {
  231. type Item = ((u32, u32), Option<(StitchType, &'a Color)>);
  232. fn next(&mut self) -> Option<Self::Item> {
  233. let px = self.values.next();
  234. if let Some(px) = px {
  235. let r = px
  236. .as_ref()
  237. .map(|stitch| (stitch.typ, &self.palette[stitch.color.idx as usize]));
  238. let ret = ((self.x, self.y), r);
  239. self.x += 1;
  240. if self.x == self.width {
  241. self.x = 0;
  242. self.y += 1;
  243. }
  244. Some(ret)
  245. } else {
  246. None
  247. }
  248. }
  249. }