1use std::io::Read;
32
33use flate2::read::ZlibDecoder;
34
35use crate::binary::{Reader, Writer, adler32};
36use crate::error::{Error, Result, bail};
37use crate::image::{GrayImage, RgbaImage};
38
39pub const BLOCK_SIZE: u32 = 256;
41pub const BLOCK_PIXELS: usize = (BLOCK_SIZE * BLOCK_SIZE) as usize;
43const ATTRIBUTE_HEADER: u32 = 16;
44const BEGIN_NAME: &str = "BlockDataBeginChunk";
45const END_NAME: &str = "BlockDataEndChunk";
46const STATUS_NAME: &str = "BlockStatus";
47const CHECKSUM_NAME: &str = "BlockCheckSum";
48const TABLE_HEADER: u32 = 12;
49const TABLE_ENTRY: u32 = 4;
50pub const EMPTY_RECORD_SIZE: u32 = 104;
52pub const RECORD_OVERHEAD: u32 = 112;
54
55#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize)]
57#[serde(rename_all = "snake_case")]
58#[non_exhaustive]
59pub enum PixelKind {
60 Color,
62 Gray,
64 Monochrome,
66 Alpha,
68 Unknown,
70}
71
72#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)]
74pub struct Section {
75 pub name: String,
77 pub values: Vec<u32>,
79}
80
81#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)]
83pub struct Attribute {
84 pub sections: Vec<Section>,
86}
87
88#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize)]
90pub struct Parameter {
91 pub width: u32,
93 pub height: u32,
95 pub blocks_x: u32,
97 pub blocks_y: u32,
99 pub channel_order: u32,
101 pub channel_bytes: u32,
103 pub image_channels: u32,
105 pub pixel_bytes: u32,
107 pub image_offset: u32,
109 pub image_pixel_bytes: u32,
111 pub image_row_bytes: u32,
113 pub alpha_pixel_bytes: u32,
115 pub alpha_row_bytes: u32,
117 pub block_pixels: u32,
119 pub block_width: u32,
121 pub block_height: u32,
123 pub block_shift_x: u32,
125 pub block_shift_y: u32,
127}
128
129impl Parameter {
130 pub fn new(kind: PixelKind, width: u32, height: u32) -> Self {
132 let (channel_order, image_channels, pixel_bytes, image_offset, image_pixel_bytes, image_row_bytes, alpha_row) =
133 match kind {
134 PixelKind::Color => (0x21, 4, 5, 65536, 4, 1024, 256),
135 PixelKind::Gray => (0x11, 1, 2, 65536, 1, 256, 256),
136 PixelKind::Monochrome => (0x11, 1, 1, 8192, 1, 32, 32),
137 PixelKind::Alpha | PixelKind::Unknown => (0x01, 0, 1, 0, 0, 0, 256),
138 };
139 Parameter {
140 width,
141 height,
142 blocks_x: width.div_ceil(BLOCK_SIZE),
143 blocks_y: height.div_ceil(BLOCK_SIZE),
144 channel_order,
145 channel_bytes: 1,
146 image_channels,
147 pixel_bytes,
148 image_offset,
149 image_pixel_bytes,
150 image_row_bytes,
151 alpha_pixel_bytes: 1,
152 alpha_row_bytes: alpha_row,
153 block_pixels: 65536,
154 block_width: BLOCK_SIZE,
155 block_height: BLOCK_SIZE,
156 block_shift_x: 8,
157 block_shift_y: 8,
158 }
159 }
160
161 pub fn kind(&self) -> PixelKind {
163 match (self.channel_order, self.image_channels, self.alpha_row_bytes) {
164 (0x21, 4, _) => PixelKind::Color,
165 (0x11, 1, 32) => PixelKind::Monochrome,
166 (0x11, 1, _) => PixelKind::Gray,
167 (0x01, 0, _) => PixelKind::Alpha,
168 _ => PixelKind::Unknown,
169 }
170 }
171
172 pub fn block_count(&self) -> usize {
174 self.blocks_x as usize * self.blocks_y as usize
175 }
176
177 pub fn block_bytes(&self) -> usize {
179 let alpha = self.alpha_row_bytes as usize * BLOCK_SIZE as usize;
180 let image = self.image_row_bytes as usize * BLOCK_SIZE as usize;
181 if self.image_channels == 0 { alpha } else { (self.image_offset as usize).max(alpha) + image }
182 }
183
184 pub fn block_origin(&self, index: usize) -> (u32, u32) {
186 let column = index % self.blocks_x.max(1) as usize;
187 let row = index / self.blocks_x.max(1) as usize;
188 (column as u32 * BLOCK_SIZE, row as u32 * BLOCK_SIZE)
189 }
190
191 fn check(&self) -> Result<()> {
192 if self.blocks_x != self.width.div_ceil(BLOCK_SIZE) || self.blocks_y != self.height.div_ceil(BLOCK_SIZE) {
193 bail!(
194 "Block grid {}×{} does not fit the size {}×{}",
195 self.blocks_x,
196 self.blocks_y,
197 self.width,
198 self.height
199 );
200 }
201 if self.block_width != BLOCK_SIZE || self.block_height != BLOCK_SIZE {
202 bail!("Unsupported block size {}×{}", self.block_width, self.block_height);
203 }
204 if self.width > 1 << 20 || self.height > 1 << 20 {
205 bail!("Offscreen too large: {}×{}", self.width, self.height);
206 }
207 Ok(())
208 }
209}
210
211#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)]
213pub struct InitColor {
214 pub do_initialize: bool,
216 pub alpha: u32,
218 pub image_values: Vec<u32>,
220}
221
222impl Attribute {
223 pub fn parse(data: &[u8]) -> Result<Self> {
225 let mut reader = Reader::new(data);
226 let header = reader.be_u32()?;
227 if header != ATTRIBUTE_HEADER {
228 bail!("Offscreen attribute starts with {header}, expected 16");
229 }
230 let lengths = [reader.be_u32()?, reader.be_u32()?, reader.be_u32()?];
231 let mut sections = Vec::new();
232 for length in lengths {
233 let start = reader.position();
234 let name = reader.utf16_name()?;
235 let end = start + length as usize;
236 if end > data.len() || end < reader.position() || !(end - reader.position()).is_multiple_of(4) {
237 bail!("Offscreen attribute section {name:?} has a bad length {length}");
238 }
239 let mut values = Vec::new();
240 while reader.position() < end {
241 values.push(reader.be_u32()?);
242 }
243 sections.push(Section { name, values });
244 }
245 if !reader.is_empty() {
246 bail!("{} bytes after the offscreen attribute sections", reader.remaining());
247 }
248 Ok(Attribute { sections })
249 }
250
251 pub fn to_bytes(&self) -> Vec<u8> {
253 let mut bodies = Vec::new();
254 for section in &self.sections {
255 let mut writer = Writer::new();
256 writer.utf16_name(§ion.name);
257 for value in §ion.values {
258 writer.be_u32(*value);
259 }
260 bodies.push(writer.into_inner());
261 }
262 let mut writer = Writer::new();
263 writer.be_u32(ATTRIBUTE_HEADER);
264 for body in &bodies {
265 writer.be_u32(body.len() as u32);
266 }
267 for body in &bodies {
268 writer.bytes(body);
269 }
270 writer.into_inner()
271 }
272
273 pub fn section(&self, name: &str) -> Option<&Section> {
275 self.sections.iter().find(|section| section.name == name)
276 }
277
278 fn section_mut(&mut self, name: &str) -> Option<&mut Section> {
279 self.sections.iter_mut().find(|section| section.name == name)
280 }
281
282 pub fn parameter(&self) -> Result<Parameter> {
284 let values = &self
285 .section("Parameter")
286 .ok_or_else(|| Error::format("Offscreen attribute has no Parameter section"))?
287 .values;
288 if values.len() < 18 {
289 bail!("Parameter section has {} values, expected at least 18", values.len());
290 }
291 let parameter = Parameter {
292 width: values[0],
293 height: values[1],
294 blocks_x: values[2],
295 blocks_y: values[3],
296 channel_order: values[4],
297 channel_bytes: values[5],
298 image_channels: values[6],
299 pixel_bytes: values[7],
300 image_offset: values[8],
301 image_pixel_bytes: values[9],
302 image_row_bytes: values[10],
303 alpha_pixel_bytes: values[11],
304 alpha_row_bytes: values[12],
305 block_pixels: values[13],
306 block_width: values[14],
307 block_height: values[15],
308 block_shift_x: values[16],
309 block_shift_y: values[17],
310 };
311 parameter.check()?;
312 Ok(parameter)
313 }
314
315 pub fn parameter_extra(&self) -> &[u32] {
318 self.section("Parameter").map(|section| section.values.get(18..).unwrap_or(&[])).unwrap_or(&[])
319 }
320
321 pub fn init_color(&self) -> Option<InitColor> {
323 let values = &self.section("InitColor")?.values;
325 let count = *values.get(3)? as usize;
326 Some(InitColor {
327 do_initialize: *values.get(1)? != 0,
328 alpha: *values.get(2)?,
329 image_values: values.get(5..5 + count)?.to_vec(),
330 })
331 }
332
333 pub fn block_sizes(&self) -> Option<&[u32]> {
335 let values = &self.section("BlockSize")?.values;
336 let count = *values.get(1)? as usize;
337 values.get(3..3 + count)
338 }
339
340 pub fn set_block_sizes(&mut self, sizes: &[u32]) {
342 let values = vec![TABLE_HEADER, sizes.len() as u32, TABLE_ENTRY].into_iter().chain(sizes.iter().copied());
343 match self.section_mut("BlockSize") {
344 Some(section) => section.values = values.collect(),
345 None => self.sections.push(Section { name: "BlockSize".into(), values: values.collect() }),
346 }
347 }
348
349 pub fn new(parameter: &Parameter, init: Option<&InitColor>) -> Self {
351 let mut values = vec![
352 parameter.width,
353 parameter.height,
354 parameter.blocks_x,
355 parameter.blocks_y,
356 parameter.channel_order,
357 parameter.channel_bytes,
358 parameter.image_channels,
359 parameter.pixel_bytes,
360 parameter.image_offset,
361 parameter.image_pixel_bytes,
362 parameter.image_row_bytes,
363 parameter.alpha_pixel_bytes,
364 parameter.alpha_row_bytes,
365 parameter.block_pixels,
366 parameter.block_width,
367 parameter.block_height,
368 parameter.block_shift_x,
369 parameter.block_shift_y,
370 ];
371 values.extend([(parameter.kind() == PixelKind::Monochrome) as u32, 0]);
372 let init_values = match init {
373 Some(init) => {
374 let mut values = vec![20, init.do_initialize as u32, init.alpha, init.image_values.len() as u32, 4];
375 values.extend(&init.image_values);
376 values
377 }
378 None => vec![20, 0, 0, 0, 4],
379 };
380 let mut attribute = Attribute {
381 sections: vec![
382 Section { name: "Parameter".into(), values },
383 Section { name: "InitColor".into(), values: init_values },
384 ],
385 };
386 attribute.set_block_sizes(&vec![EMPTY_RECORD_SIZE; parameter.block_count()]);
387 attribute
388 }
389}
390
391#[derive(Debug, Clone, PartialEq, Eq)]
393pub struct Block {
394 pub index: u32,
396 pub raw_size: u32,
398 pub width: u32,
400 pub height: u32,
402 pub zlib: Option<Vec<u8>>,
404}
405
406impl Block {
407 pub fn record_size(&self) -> u32 {
409 match &self.zlib {
410 Some(zlib) => RECORD_OVERHEAD + zlib.len() as u32,
411 None => EMPTY_RECORD_SIZE,
412 }
413 }
414
415 pub fn checksum(&self) -> u32 {
417 match &self.zlib {
418 Some(zlib) => {
419 let mut stored = (zlib.len() as u32).to_le_bytes().to_vec();
420 stored.extend_from_slice(zlib);
421 adler32(&stored)
422 }
423 None => 0,
424 }
425 }
426
427 pub fn inflate(&self) -> Result<Option<Vec<u8>>> {
429 let Some(zlib) = &self.zlib else { return Ok(None) };
430 let mut raw = Vec::with_capacity(self.raw_size as usize);
431 ZlibDecoder::new(zlib.as_slice())
432 .take(self.raw_size as u64 + 1)
433 .read_to_end(&mut raw)
434 .map_err(|_| Error::format(format!("Block {} is not valid zlib data", self.index)))?;
435 if raw.len() != self.raw_size as usize {
436 bail!("Block {} has {} bytes, expected {}", self.index, raw.len(), self.raw_size);
437 }
438 Ok(Some(raw))
439 }
440}
441
442#[derive(Debug, Clone, PartialEq, Eq)]
444pub struct Table {
445 pub name: String,
447 pub header: u32,
449 pub entry_size: u32,
451 pub values: Vec<u32>,
453}
454
455#[derive(Debug, Clone, PartialEq, Eq)]
457pub struct BlockData {
458 pub blocks: Vec<Block>,
460 pub tables: Vec<Table>,
462}
463
464impl BlockData {
465 pub fn parse(payload: &[u8]) -> Result<Self> {
467 let mut reader = Reader::new(payload);
468 let length = reader.be_u64()?;
469 if length != (payload.len() - 8) as u64 {
470 bail!("Block data says {length} bytes follow, {} do", payload.len() - 8);
471 }
472 let mut blocks = Vec::new();
473 let mut tables = Vec::new();
474 while !reader.is_empty() {
475 let start = reader.position();
476 let peek = Reader::at(payload, start + 4);
477 if peek.peek_utf16_name().as_deref() == Some(BEGIN_NAME) {
478 let record_length = reader.be_u32()? as usize;
479 reader.utf16_name()?;
480 let index = reader.be_u32()?;
481 let raw_size = reader.be_u32()?;
482 let width = reader.be_u32()?;
483 let height = reader.be_u32()?;
484 let has_data = reader.be_u32()?;
485 let zlib = match has_data {
486 0 => None,
487 1 => {
488 let stored = reader.be_u32()? as usize;
489 let zlib_length = reader.le_u32()? as usize;
490 if stored != zlib_length + 4 {
491 bail!("Block {index}: stored length {stored} vs zlib length {zlib_length}");
492 }
493 Some(reader.bytes(zlib_length)?.to_vec())
494 }
495 other => bail!("Block {index}: unknown data flag {other}"),
496 };
497 if reader.utf16_name()? != END_NAME {
498 bail!("Block {index}: record does not end with {END_NAME}");
499 }
500 if reader.position() - start != record_length {
501 bail!("Block {index}: record is {} bytes, says {record_length}", reader.position() - start);
502 }
503 blocks.push(Block { index, raw_size, width, height, zlib });
504 } else {
505 let name = reader.utf16_name()?;
506 let header = reader.be_u32()?;
507 let count = reader.be_u32()? as usize;
508 let entry_size = reader.be_u32()?;
509 if entry_size != 4 || count > reader.remaining() / 4 {
510 bail!("Table {name:?} has entries of {entry_size} bytes × {count}");
511 }
512 let mut values = Vec::with_capacity(count);
513 for _ in 0..count {
514 values.push(reader.be_u32()?);
515 }
516 tables.push(Table { name, header, entry_size, values });
517 }
518 }
519 Ok(BlockData { blocks, tables })
520 }
521
522 pub fn to_bytes(&self) -> Vec<u8> {
524 let mut writer = Writer::new();
525 writer.be_u64(0);
526 for block in &self.blocks {
527 writer.be_u32(block.record_size()).utf16_name(BEGIN_NAME);
528 writer
529 .be_u32(block.index)
530 .be_u32(block.raw_size)
531 .be_u32(block.width)
532 .be_u32(block.height)
533 .be_u32(block.zlib.is_some() as u32);
534 if let Some(zlib) = &block.zlib {
535 writer.be_u32(zlib.len() as u32 + 4).le_u32(zlib.len() as u32).bytes(zlib);
536 }
537 writer.utf16_name(END_NAME);
538 }
539 for table in &self.tables {
540 writer.utf16_name(&table.name).be_u32(table.header).be_u32(table.values.len() as u32);
541 writer.be_u32(table.entry_size);
542 for value in &table.values {
543 writer.be_u32(*value);
544 }
545 }
546 let mut data = writer.into_inner();
547 let length = (data.len() - 8) as u64;
548 data[..8].copy_from_slice(&length.to_be_bytes());
549 data
550 }
551
552 pub fn table(&self, name: &str) -> Option<&Table> {
554 self.tables.iter().find(|table| table.name == name)
555 }
556
557 pub fn checksum_mismatches(&self) -> Vec<u32> {
559 let Some(table) = self.table(CHECKSUM_NAME) else { return Vec::new() };
560 self.blocks
561 .iter()
562 .zip(&table.values)
563 .filter(|(block, stored)| block.checksum() != **stored)
564 .map(|(block, _)| block.index)
565 .collect()
566 }
567
568 pub fn from_raw_blocks(raw_blocks: &[Option<Vec<u8>>], raw_size: usize) -> Result<Self> {
570 let mut blocks = Vec::with_capacity(raw_blocks.len());
571 for (index, raw) in raw_blocks.iter().enumerate() {
572 if let Some(raw) = raw
573 && raw.len() != raw_size
574 {
575 bail!("Block {index} has {} bytes, expected {raw_size}", raw.len());
576 }
577 blocks.push(Block {
578 index: index as u32,
579 raw_size: raw_size as u32,
580 width: BLOCK_SIZE,
581 height: BLOCK_SIZE,
582 zlib: raw.as_ref().map(|raw| compress(raw)),
583 });
584 }
585 let checksums = blocks.iter().map(Block::checksum).collect();
586 Ok(BlockData {
587 tables: vec![
588 Table {
589 name: STATUS_NAME.into(),
590 header: TABLE_HEADER,
591 entry_size: TABLE_ENTRY,
592 values: vec![1; blocks.len()],
593 },
594 Table { name: CHECKSUM_NAME.into(), header: TABLE_HEADER, entry_size: TABLE_ENTRY, values: checksums },
595 ],
596 blocks,
597 })
598 }
599
600 pub fn record_sizes(&self) -> Vec<u32> {
602 self.blocks.iter().map(Block::record_size).collect()
603 }
604}
605
606pub fn compress(raw: &[u8]) -> Vec<u8> {
609 crate::zlib::compress(raw).expect("compressing in memory")
610}
611
612#[derive(Debug, Clone)]
614pub struct Offscreen {
615 pub attribute: Attribute,
617 pub parameter: Parameter,
619 pub data: Option<BlockData>,
621}
622
623impl Offscreen {
624 pub fn parse(attribute: &[u8], payload: Option<&[u8]>) -> Result<Self> {
626 let attribute = Attribute::parse(attribute)?;
627 let parameter = attribute.parameter()?;
628 let data = payload.map(BlockData::parse).transpose()?;
629 if let Some(data) = &data
630 && data.blocks.len() != parameter.block_count()
631 {
632 bail!("Block data has {} blocks, the grid {}", data.blocks.len(), parameter.block_count());
633 }
634 Ok(Offscreen { attribute, parameter, data })
635 }
636
637 pub fn kind(&self) -> PixelKind {
639 self.parameter.kind()
640 }
641
642 pub fn for_each_block(&self, mut visit: impl FnMut(usize, &[u8]) -> Result<()>) -> Result<()> {
644 let Some(data) = &self.data else { return Ok(()) };
645 let expected = self.parameter.block_bytes();
646 for (index, block) in data.blocks.iter().enumerate() {
647 if let Some(raw) = block.inflate()? {
648 if raw.len() < expected {
649 bail!("Block {index} has {} bytes, the layout needs {expected}", raw.len());
650 }
651 visit(index, &raw)?;
652 }
653 }
654 Ok(())
655 }
656
657 pub fn to_rgba(&self) -> Result<RgbaImage> {
660 let p = self.parameter;
661 let kind = p.kind();
662 if !matches!(kind, PixelKind::Color | PixelKind::Gray | PixelKind::Monochrome | PixelKind::Alpha) {
663 bail!("Unknown pixel layout (channel order {:#x})", p.channel_order);
664 }
665 let mut image = RgbaImage::new(p.width, p.height);
666 if let Some(init) = self.attribute.init_color()
667 && init.do_initialize
668 {
669 let alpha = (init.alpha & 0xff) as u8;
670 image.fill([255, 255, 255, alpha]);
671 }
672 self.for_each_block(|index, raw| {
673 let (left, top) = p.block_origin(index);
674 let width = BLOCK_SIZE.min(p.width - left) as usize;
675 let height = BLOCK_SIZE.min(p.height - top) as usize;
676 for y in 0..height {
677 let row = image.row_mut(top + y as u32);
678 for x in 0..width {
679 let alpha = plane_value(raw, 0, p.alpha_row_bytes, x, y);
680 let target = (left as usize + x) * 4;
681 let rgb = match kind {
682 PixelKind::Color => {
683 let at = p.image_offset as usize + y * p.image_row_bytes as usize + x * 4;
684 [raw[at + 2], raw[at + 1], raw[at]]
685 }
686 PixelKind::Gray | PixelKind::Monochrome => {
687 let value = plane_value(raw, p.image_offset as usize, p.image_row_bytes, x, y);
688 [value; 3]
689 }
690 _ => [0, 0, 0],
691 };
692 row[target..target + 3].copy_from_slice(&rgb);
693 row[target + 3] = alpha;
694 }
695 }
696 Ok(())
697 })?;
698 Ok(image)
699 }
700
701 pub fn stored_boxes(&self) -> Vec<(u32, u32, u32, u32)> {
704 let p = self.parameter;
705 let Some(data) = &self.data else { return Vec::new() };
706 data.blocks
707 .iter()
708 .enumerate()
709 .filter(|(_, block)| block.zlib.is_some())
710 .map(|(index, _)| {
711 let (left, top) = p.block_origin(index);
712 (left, top, (left + BLOCK_SIZE).min(p.width), (top + BLOCK_SIZE).min(p.height))
713 })
714 .collect()
715 }
716
717 pub fn to_rgba_region(&self, region: (i64, i64, i64, i64)) -> Result<RgbaImage> {
722 let p = self.parameter;
723 if p.kind() == PixelKind::Unknown {
724 bail!("Unknown pixel layout (channel order {:#x})", p.channel_order);
725 }
726 let (x0, y0, x1, y1) = region;
727 let mut image = RgbaImage::new((x1 - x0).max(0) as u32, (y1 - y0).max(0) as u32);
728 let inside = (x0.max(0), y0.max(0), x1.min(p.width as i64), y1.min(p.height as i64));
730 if inside.0 >= inside.2 || inside.1 >= inside.3 {
731 return Ok(image);
732 }
733 if let Some(init) = self.attribute.init_color()
735 && init.do_initialize
736 {
737 let mut fill = RgbaImage::new((inside.2 - inside.0) as u32, (inside.3 - inside.1) as u32);
738 fill.fill([255, 255, 255, (init.alpha & 0xff) as u8]);
739 image.paste(&fill, inside.0 - x0, inside.1 - y0);
740 }
741 let Some(data) = &self.data else { return Ok(image) };
742 let expected = p.block_bytes();
743 let size = BLOCK_SIZE as i64;
744 let columns = (inside.0 / size)..(inside.2 - 1) / size + 1;
745 for row in (inside.1 / size)..(inside.3 - 1) / size + 1 {
746 for column in columns.clone() {
747 let index = (row * p.blocks_x as i64 + column) as usize;
748 let Some(raw) = data.blocks.get(index).map(Block::inflate).transpose()?.flatten() else { continue };
749 if raw.len() < expected {
750 bail!("Block {index} has {} bytes, the layout needs {expected}", raw.len());
751 }
752 let (left, top) = p.block_origin(index);
753 let tile = block_to_rgba(&p, BLOCK_SIZE.min(p.width - left), BLOCK_SIZE.min(p.height - top), &raw);
754 image.paste(&tile, left as i64 - x0, top as i64 - y0);
755 }
756 }
757 Ok(image)
758 }
759
760 pub fn to_rgba_halved(&self, halvings: u32) -> Result<RgbaImage> {
766 self.halved(halvings, false)
767 }
768
769 pub fn to_mask_rgba_halved(&self, halvings: u32) -> Result<RgbaImage> {
772 self.halved(halvings, true)
773 }
774
775 fn halved(&self, halvings: u32, mask: bool) -> Result<RgbaImage> {
776 let p = self.parameter;
777 let within = halvings.min(BLOCK_SIZE.trailing_zeros());
778 let reduce = |mut v: u32| {
779 for _ in 0..within {
780 v = (v / 2).max(1);
781 }
782 v
783 };
784 let mut image = RgbaImage::new(reduce(p.width), reduce(p.height));
785 let init = self.attribute.init_color().filter(|init| init.do_initialize).map(|init| (init.alpha & 0xff) as u8);
786 match (init, mask) {
787 (Some(alpha), false) => image.fill([255, 255, 255, alpha]),
788 (alpha, true) => image.fill([alpha.unwrap_or(0), alpha.unwrap_or(0), alpha.unwrap_or(0), 255]),
789 (None, false) => {}
790 }
791 self.for_each_block(|index, raw| {
792 let (left, top) = p.block_origin(index);
793 let (width, height) = (BLOCK_SIZE.min(p.width - left), BLOCK_SIZE.min(p.height - top));
794 let mut tile =
795 if mask { block_to_mask(&p, width, height, raw) } else { block_to_rgba(&p, width, height, raw) };
796 for _ in 0..within {
797 tile = tile.halve();
798 }
799 image.paste(&tile, (left >> within) as i64, (top >> within) as i64);
800 Ok(())
801 })?;
802 for _ in within..halvings {
803 image = image.halve();
804 }
805 Ok(image)
806 }
807
808 pub fn to_alpha(&self) -> Result<GrayImage> {
810 let p = self.parameter;
811 let mut image = GrayImage::new(p.width, p.height);
812 if let Some(init) = self.attribute.init_color()
813 && init.do_initialize
814 {
815 image.pixels.fill((init.alpha & 0xff) as u8);
816 }
817 self.for_each_block(|index, raw| {
818 let (left, top) = p.block_origin(index);
819 let width = BLOCK_SIZE.min(p.width - left) as usize;
820 let height = BLOCK_SIZE.min(p.height - top) as usize;
821 for y in 0..height {
822 let start = ((top as usize + y) * p.width as usize) + left as usize;
823 for x in 0..width {
824 image.pixels[start + x] = plane_value(raw, 0, p.alpha_row_bytes, x, y);
825 }
826 }
827 Ok(())
828 })?;
829 Ok(image)
830 }
831
832 pub fn reencode_check(&self) -> Result<(usize, usize, usize)> {
835 let Some(data) = &self.data else { return Ok((0, 0, 0)) };
836 let (mut same_bytes, mut same_pixels, mut stored) = (0, 0, 0);
837 for block in &data.blocks {
838 let Some(raw) = block.inflate()? else { continue };
839 stored += 1;
840 let zlib = compress(&raw);
841 if Some(&zlib) == block.zlib.as_ref() {
842 same_bytes += 1;
843 }
844 let again = Block { zlib: Some(zlib), ..block.clone() }.inflate()?;
845 if again.as_deref() == Some(raw.as_slice()) {
846 same_pixels += 1;
847 }
848 }
849 Ok((same_bytes, same_pixels, stored))
850 }
851}
852
853fn block_to_rgba(p: &Parameter, width: u32, height: u32, raw: &[u8]) -> RgbaImage {
855 let kind = p.kind();
856 let mut image = RgbaImage::new(width, height);
857 for y in 0..height as usize {
858 let row = image.row_mut(y as u32);
859 for x in 0..width as usize {
860 let rgb = match kind {
861 PixelKind::Color => {
862 let at = p.image_offset as usize + y * p.image_row_bytes as usize + x * 4;
863 [raw[at + 2], raw[at + 1], raw[at]]
864 }
865 PixelKind::Gray | PixelKind::Monochrome => {
866 [plane_value(raw, p.image_offset as usize, p.image_row_bytes, x, y); 3]
867 }
868 _ => [0, 0, 0],
869 };
870 row[x * 4..x * 4 + 3].copy_from_slice(&rgb);
871 row[x * 4 + 3] = plane_value(raw, 0, p.alpha_row_bytes, x, y);
872 }
873 }
874 image
875}
876
877fn block_to_mask(p: &Parameter, width: u32, height: u32, raw: &[u8]) -> RgbaImage {
879 let mut image = RgbaImage::new(width, height);
880 for y in 0..height as usize {
881 let row = image.row_mut(y as u32);
882 for x in 0..width as usize {
883 let v = plane_value(raw, 0, p.alpha_row_bytes, x, y);
884 row[x * 4..x * 4 + 4].copy_from_slice(&[v, v, v, 255]);
885 }
886 }
887 image
888}
889
890fn plane_value(raw: &[u8], offset: usize, row_bytes: u32, x: usize, y: usize) -> u8 {
893 if row_bytes == BLOCK_SIZE / 8 {
894 let byte = raw[offset + y * row_bytes as usize + x / 8];
895 if byte & (0x80 >> (x % 8)) != 0 { 255 } else { 0 }
896 } else {
897 raw[offset + y * row_bytes as usize + x]
898 }
899}
900
901pub fn encode(parameter: &Parameter, rgba: Option<&RgbaImage>, alpha: Option<&GrayImage>) -> Result<BlockData> {
904 let kind = parameter.kind();
905 let raw_size = parameter.block_bytes();
906 let mut raw_blocks = Vec::with_capacity(parameter.block_count());
907 for index in 0..parameter.block_count() {
908 let (left, top) = parameter.block_origin(index);
909 let mut raw = vec![0u8; raw_size];
910 let mut visible = false;
911 for y in 0..BLOCK_SIZE {
912 let source_y = top + y;
913 if source_y >= parameter.height {
914 break;
915 }
916 for x in 0..BLOCK_SIZE {
917 let source_x = left + x;
918 if source_x >= parameter.width {
919 break;
920 }
921 let at = (y * BLOCK_SIZE + x) as usize;
922 match (kind, rgba, alpha) {
923 (PixelKind::Color, Some(image), _) => {
924 let pixel = image.pixel(source_x, source_y);
925 raw[at] = pixel[3];
926 let base = parameter.image_offset as usize + at * 4;
927 raw[base..base + 4].copy_from_slice(&[pixel[2], pixel[1], pixel[0], 0]);
928 visible |= pixel[3] != 0;
929 }
930 (PixelKind::Gray, Some(image), _) => {
931 let pixel = image.pixel(source_x, source_y);
932 raw[at] = pixel[3];
933 raw[parameter.image_offset as usize + at] = crate::image::luma(pixel[0], pixel[1], pixel[2]);
934 visible |= pixel[3] != 0;
935 }
936 (PixelKind::Alpha, _, Some(image)) => {
937 let value = image.pixels[(source_y * image.width + source_x) as usize];
938 raw[at] = value;
939 visible |= value != 0;
940 }
941 _ => bail!("Encoding {kind:?} offscreens from this input is not supported"),
942 }
943 }
944 }
945 raw_blocks.push(visible.then_some(raw));
946 }
947 BlockData::from_raw_blocks(&raw_blocks, raw_size)
948}
949
950#[cfg(test)]
951mod tests {
952 use super::*;
953
954 #[test]
955 fn colour_offscreens_round_trip() {
956 let mut image = RgbaImage::new(300, 10);
957 image.set_pixel(299, 9, [10, 20, 30, 40]);
958 image.set_pixel(0, 0, [200, 100, 50, 255]);
959 let parameter = Parameter::new(PixelKind::Color, 300, 10);
960 let data = encode(¶meter, Some(&image), None).unwrap();
961 assert_eq!(data.blocks.len(), 2);
962 assert!(data.checksum_mismatches().is_empty());
963 let bytes = data.to_bytes();
964 assert_eq!(BlockData::parse(&bytes).unwrap(), data);
965 let mut attribute = Attribute::new(¶meter, None);
966 attribute.set_block_sizes(&data.record_sizes());
967 let attribute_bytes = attribute.to_bytes();
968 assert_eq!(Attribute::parse(&attribute_bytes).unwrap(), attribute);
969 let offscreen = Offscreen::parse(&attribute_bytes, Some(&bytes)).unwrap();
970 assert_eq!(offscreen.kind(), PixelKind::Color);
971 let decoded = offscreen.to_rgba().unwrap();
972 assert_eq!(decoded.pixel(299, 9), [10, 20, 30, 40]);
973 assert_eq!(decoded.pixel(0, 0), [200, 100, 50, 255]);
974 assert_eq!(decoded.pixel(1, 1)[3], 0);
975 assert_eq!(offscreen.reencode_check().unwrap(), (2, 2, 2));
976 }
977
978 #[test]
979 fn regions_decode_only_what_they_cover() {
980 let mut image = RgbaImage::new(600, 300);
982 for (x, y) in [(0, 0), (255, 255), (256, 0), (599, 299), (300, 280)] {
983 image.set_pixel(x, y, [x as u8, y as u8, 7, 200]);
984 }
985 let parameter = Parameter::new(PixelKind::Color, 600, 300);
986 let data = encode(¶meter, Some(&image), None).unwrap();
987 let mut attribute = Attribute::new(¶meter, None);
988 attribute.set_block_sizes(&data.record_sizes());
989 let offscreen = Offscreen::parse(&attribute.to_bytes(), Some(&data.to_bytes())).unwrap();
990 assert_eq!(
992 offscreen.stored_boxes(),
993 [(0, 0, 256, 256), (256, 0, 512, 256), (256, 256, 512, 300), (512, 256, 600, 300)]
994 );
995 let whole = offscreen.to_rgba().unwrap();
996 for region in
997 [(0, 0, 600, 300), (250, 250, 310, 290), (-10, -20, 5, 5), (590, 290, 700, 400), (700, 0, 710, 10)]
998 {
999 let part = offscreen.to_rgba_region(region).unwrap();
1000 assert_eq!((part.width as i64, part.height as i64), (region.2 - region.0, region.3 - region.1));
1001 for y in 0..part.height as i64 {
1002 for x in 0..part.width as i64 {
1003 let (cx, cy) = (x + region.0, y + region.1);
1004 let expected = if (0..600).contains(&cx) && (0..300).contains(&cy) {
1005 whole.pixel(cx as u32, cy as u32)
1006 } else {
1007 [0; 4]
1008 };
1009 assert_eq!(part.pixel(x as u32, y as u32), expected, "{region:?} at {cx},{cy}");
1010 }
1011 }
1012 }
1013 }
1014
1015 #[test]
1016 fn regions_keep_the_init_colour_inside_only() {
1017 let parameter = Parameter::new(PixelKind::Color, 100, 100);
1018 let init = InitColor { do_initialize: true, alpha: 0xffff_ffff, image_values: vec![0xffff_ffff; 4] };
1019 let attribute = Attribute::new(¶meter, Some(&init));
1020 let offscreen = Offscreen::parse(&attribute.to_bytes(), None).unwrap();
1021 let part = offscreen.to_rgba_region((90, 90, 110, 110)).unwrap();
1022 assert_eq!(part.pixel(0, 0), [255, 255, 255, 255]);
1023 assert_eq!(part.pixel(9, 9), [255, 255, 255, 255]);
1024 assert_eq!(part.pixel(10, 10), [0; 4]);
1025 assert!(offscreen.stored_boxes().is_empty());
1026 }
1027
1028 #[test]
1029 fn masks_round_trip() {
1030 let mut mask = GrayImage::new(256, 256);
1031 mask.pixels[5] = 128;
1032 let parameter = Parameter::new(PixelKind::Alpha, 256, 256);
1033 let data = encode(¶meter, None, Some(&mask)).unwrap();
1034 let offscreen = Offscreen { attribute: Attribute::new(¶meter, None), parameter, data: Some(data) };
1035 assert_eq!(offscreen.to_alpha().unwrap().pixels, mask.pixels);
1036 }
1037
1038 #[test]
1039 fn halving_by_blocks_equals_halving_the_image() {
1040 let (width, height) = (601, 300);
1041 let mut image = RgbaImage::new(width, height);
1042 for y in 0..height {
1043 for x in 0..width {
1044 let v = (x * 7 + y * 13) as u8;
1045 let alpha = if (x / 37 + y / 23) % 3 == 0 { 0 } else { v / 2 + 100 };
1046 image.set_pixel(x, y, [v, 255 - v, (x ^ y) as u8, alpha]);
1047 }
1048 }
1049 let parameter = Parameter::new(PixelKind::Color, width, height);
1050 let data = encode(¶meter, Some(&image), None).unwrap();
1051 let mut attribute = Attribute::new(¶meter, None);
1052 attribute.set_block_sizes(&data.record_sizes());
1053 let offscreen = Offscreen::parse(&attribute.to_bytes(), Some(&data.to_bytes())).unwrap();
1054 let mut expected = offscreen.to_rgba().unwrap();
1055 for halvings in 1..=10 {
1056 expected = expected.halve();
1057 assert_eq!(offscreen.to_rgba_halved(halvings).unwrap(), expected, "{halvings}");
1058 }
1059 }
1060
1061 #[test]
1062 fn broken_payloads_are_errors() {
1063 let parameter = Parameter::new(PixelKind::Color, 16, 16);
1064 let mut image = RgbaImage::new(16, 16);
1065 image.fill([1, 2, 3, 4]);
1066 let bytes = encode(¶meter, Some(&image), None).unwrap().to_bytes();
1067 for cut in [4, 20, bytes.len() / 2, bytes.len() - 1] {
1068 assert!(BlockData::parse(&bytes[..cut]).is_err(), "cut {cut}");
1069 }
1070 }
1071}