From 45a8e0227adc229a96aa14b8a57fd9a741d1b13c Mon Sep 17 00:00:00 2001 From: lifegpc Date: Thu, 25 Sep 2025 09:46:53 +0800 Subject: [PATCH] Add support for circus crx image version 1 --- README.md | 2 +- src/scripts/circus/image/crx.rs | 432 +++++++++++++++++++++++++++----- src/utils/img.rs | 191 ++++++++++++++ 3 files changed, 558 insertions(+), 67 deletions(-) diff --git a/README.md b/README.md index fed9ecc..9b09368 100644 --- a/README.md +++ b/README.md @@ -125,7 +125,7 @@ msg-tool create -t | Image Type | Feature Name | Name | Export | Import | Export Multiple | Import Multiple | Create | Remarks | |---|---|---|---|---|---|---|---|---| -| `circus-crx` | `circus-img` | Circus Image File (.crx) | ✔️ | ✔️ | ❌ | ❌ | ✔️ | | +| `circus-crx` | `circus-img` | Circus Image File (.crx) | ✔️ | ✔️ | ❌ | ❌ | ✔️ | V1 is not supported when importing/creating image | | `circus-crxd` | `circus-img` | Circus Differential Image File (.crx) | ✔️ | ❌ | ❌ | ❌ | ❌ | | ### Emote | Script Type | Feature Name | Name | Export | Import | Export Multiple | Import Multiple | Custom Export | Custom Import | Create | Remarks | diff --git a/src/scripts/circus/image/crx.rs b/src/scripts/circus/image/crx.rs index 6ebd9db..60f7c9c 100644 --- a/src/scripts/circus/image/crx.rs +++ b/src/scripts/circus/image/crx.rs @@ -178,11 +178,30 @@ struct Header { clips: Vec, } +#[derive(Clone, Debug)] +enum CrxImageData { + RowEncoded(Vec), + IndexedV1 { + pixels: Vec, + stride: usize, + palette: Vec, + palette_format: PaletteFormat, + pixel_depth_bits: usize, + }, + Direct(Vec), +} + +impl CrxImageData { + fn is_row_encoded(&self) -> bool { + matches!(self, CrxImageData::RowEncoded(_)) + } +} + /// Circus CRX Image pub struct CrxImage { header: Header, color_type: ImageColorType, - data: Vec, + data: CrxImageData, compress_level: u32, keep_original_bpp: bool, zstd: bool, @@ -193,10 +212,17 @@ pub struct CrxImage { impl std::fmt::Debug for CrxImage { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let data_info = match &self.data { + CrxImageData::RowEncoded(buf) => format!("row-encoded({})", buf.len()), + CrxImageData::IndexedV1 { pixels, .. } => { + format!("indexed-v1({})", pixels.len()) + } + CrxImageData::Direct(buf) => format!("direct({})", buf.len()), + }; f.debug_struct("CrxImage") .field("header", &self.header) .field("color_type", &self.color_type) - .field("data_length", &self.data.len()) + .field("data", &data_info) .finish() } } @@ -214,41 +240,132 @@ impl CrxImage { return Err(anyhow::anyhow!("Invalid CRX image magic")); } let header: Header = reader.read_struct(false, Encoding::Utf8)?; - if header.version < 2 || header.version > 3 { + if header.version == 0 || header.version > 3 { return Err(anyhow::anyhow!( "Unsupported CRX version: {}", header.version )); } - let color_type = if header.bpp == 0 { - ImageColorType::Bgr - } else if header.bpp == 1 { - ImageColorType::Bgra + + let (color_type, data) = if header.version == 1 { + let width = usize::from(header.width); + let height = usize::from(header.height); + if width == 0 || height == 0 { + return Err(anyhow::anyhow!("CRX v1 image has zero dimensions")); + } + + let bits_per_pixel = match header.bpp { + 0 => 24usize, + 1 => 32usize, + _ => 8usize, + }; + if bits_per_pixel % 8 != 0 { + return Err(anyhow::anyhow!( + "Unsupported bits per pixel {} for CRX v1", + bits_per_pixel + )); + } + let pixel_size = bits_per_pixel / 8; + if pixel_size == 0 { + return Err(anyhow::anyhow!("Invalid pixel size for CRX v1 image")); + } + + let row_bytes = width + .checked_mul(pixel_size) + .ok_or_else(|| anyhow::anyhow!("CRX v1 row size overflow"))?; + let stride = (row_bytes + .checked_add(3) + .ok_or_else(|| anyhow::anyhow!("CRX v1 stride overflow"))?) + & !3usize; + let output_len = stride + .checked_mul(height) + .ok_or_else(|| anyhow::anyhow!("CRX v1 buffer size overflow"))?; + + let palette = if bits_per_pixel == 8 { + let raw_colors = usize::from(header.bpp); + Some(( + Self::read_v1_palette(&mut reader, raw_colors)?, + PaletteFormat::Rgb, + )) + } else { + None + }; + + if (header.flags & 0x10) != 0 { + reader.read_u32()?; // stored compressed size, ignored for v1 + } + + let pixels = Self::unpack_v1(&mut reader, output_len)?; + + if let Some((palette, palette_format)) = palette { + let data = CrxImageData::IndexedV1 { + pixels, + stride, + palette, + palette_format, + pixel_depth_bits: bits_per_pixel, + }; + (ImageColorType::Bgr, data) + } else { + let mut trimmed = Vec::with_capacity( + row_bytes + .checked_mul(height) + .ok_or_else(|| anyhow::anyhow!("CRX v1 buffer size overflow"))?, + ); + for row in 0..height { + let start = row + .checked_mul(stride) + .ok_or_else(|| anyhow::anyhow!("CRX v1 row offset overflow"))?; + let end = start + .checked_add(row_bytes) + .ok_or_else(|| anyhow::anyhow!("CRX v1 row slice overflow"))?; + if end > pixels.len() { + return Err(anyhow::anyhow!( + "CRX v1 image data is shorter than expected" + )); + } + trimmed.extend_from_slice(&pixels[start..end]); + } + let color_type = match bits_per_pixel { + 24 => ImageColorType::Bgr, + 32 => ImageColorType::Bgra, + _ => ImageColorType::Bgr, + }; + (color_type, CrxImageData::Direct(trimmed)) + } } else { - return Err(anyhow::anyhow!("Unsupported CRX bpp: {}", header.bpp)); - }; - let compressed_size = if (header.flags & 0x10) == 0 { - let len = reader.stream_length()?; - (len - reader.stream_position()?) as u32 - } else { - reader.read_u32()? - }; - let compressed_data = reader.read_exact_vec(compressed_size as usize)?; - let uncompessed = if compressed_data.starts_with(&[0x28, 0xb5, 0x2f, 0xfd]) { - let mut decoder = zstd::Decoder::new(MemReaderRef::new(&compressed_data))?; - let mut decompressed_data = Vec::new(); - decoder.read_to_end(&mut decompressed_data)?; - decompressed_data - } else { - let mut decompressed_data = Vec::new(); - flate2::read::ZlibDecoder::new(MemReaderRef::new(&compressed_data)) - .read_to_end(&mut decompressed_data)?; - decompressed_data + let color_type = if header.bpp == 0 { + ImageColorType::Bgr + } else if header.bpp == 1 { + ImageColorType::Bgra + } else { + return Err(anyhow::anyhow!("Unsupported CRX bpp: {}", header.bpp)); + }; + let compressed_size = if (header.flags & 0x10) == 0 { + let len = reader.stream_length()?; + (len - reader.stream_position()?) as u32 + } else { + reader.read_u32()? + }; + let compressed_data = reader.read_exact_vec(compressed_size as usize)?; + let uncompressed = if compressed_data.starts_with(&[0x28, 0xb5, 0x2f, 0xfd]) { + let mut decoder = zstd::Decoder::new(MemReaderRef::new(&compressed_data))?; + let mut decompressed_data = Vec::new(); + decoder.read_to_end(&mut decompressed_data)?; + decompressed_data + } else { + let mut decompressed_data = Vec::new(); + flate2::read::ZlibDecoder::new(MemReaderRef::new(&compressed_data)) + .read_to_end(&mut decompressed_data)?; + decompressed_data + }; + (color_type, CrxImageData::RowEncoded(uncompressed)) }; + Ok(CrxImage { header, color_type, - data: uncompessed, + data, compress_level: config.zlib_compression_level, keep_original_bpp: config.circus_crx_keep_original_bpp, zstd: config.circus_crx_zstd, @@ -456,6 +573,101 @@ impl CrxImage { Ok(src_p) } + fn read_v1_palette(reader: &mut T, raw_colors: usize) -> Result> { + if raw_colors == 0 { + return Err(anyhow::anyhow!("CRX v1 palette has zero colors")); + } + let color_size = if raw_colors == 0x0102 { 4usize } else { 3usize }; + let mut colors = raw_colors; + if colors > 0x0100 { + colors = 0x0100; + } + let palette_size = colors + .checked_mul(color_size) + .ok_or_else(|| anyhow::anyhow!("CRX v1 palette size overflow"))?; + if palette_size == 0 { + return Err(anyhow::anyhow!("CRX v1 palette size is zero")); + } + let mut palette_raw = vec![0u8; palette_size]; + reader.read_exact(&mut palette_raw)?; + let mut palette = Vec::with_capacity(colors * 3); + let mut pos = 0usize; + while pos < palette_raw.len() { + let r = palette_raw[pos]; + let mut g = palette_raw[pos + 1]; + let b = palette_raw[pos + 2]; + if b == 0xFF && g == 0x00 && r == 0xFF { + g = 0xFF; + } + palette.push(r); + palette.push(g); + palette.push(b); + pos += color_size; + } + Ok(palette) + } + + fn unpack_v1(reader: &mut T, output_len: usize) -> Result> { + const WINDOW_SIZE: usize = 0x10000; + const WINDOW_MASK: usize = WINDOW_SIZE - 1; + let mut window = vec![0u8; WINDOW_SIZE]; + let mut win_pos: usize = 0; + let mut dst = vec![0u8; output_len]; + let mut dst_pos = 0usize; + let mut flag: u16 = 0; + while dst_pos < output_len { + flag >>= 1; + if (flag & 0x100) == 0 { + let next = reader.read_u8()? as u16; + flag = next | 0xFF00; + } + if (flag & 1) != 0 { + let byte = reader.read_u8()?; + window[win_pos] = byte; + win_pos = (win_pos + 1) & WINDOW_MASK; + dst[dst_pos] = byte; + dst_pos += 1; + } else { + let control = reader.read_u8()?; + let (count, offset_value) = if control >= 0xC0 { + let next = reader.read_u8()? as usize; + let offset = (((control as usize) & 0x03) << 8) | next; + let count = 4 + (((control as usize) >> 2) & 0x0F); + (count, offset) + } else if (control & 0x80) != 0 { + let mut offset = (control & 0x1F) as usize; + let count = 2 + (((control as usize) >> 5) & 0x03); + if offset == 0 { + offset = reader.read_u8()? as usize; + } + (count, offset) + } else if control == 0x7F { + let count = 2 + reader.read_u16()? as usize; + let offset = reader.read_u16()? as usize; + (count, offset) + } else { + let offset = reader.read_u16()? as usize; + let count = control as usize + 4; + (count, offset) + }; + + let mut offset_pos = (win_pos.wrapping_sub(offset_value)) & WINDOW_MASK; + for _ in 0..count { + if dst_pos >= output_len { + break; + } + let value = window[offset_pos]; + offset_pos = (offset_pos + 1) & WINDOW_MASK; + window[win_pos] = value; + win_pos = (win_pos + 1) & WINDOW_MASK; + dst[dst_pos] = value; + dst_pos += 1; + } + } + } + Ok(dst) + } + fn decode_image( dst: &mut Vec, src: &[u8], @@ -823,39 +1035,106 @@ impl Script for CrxImage { } fn export_image(&self) -> Result { - let data_size = self.color_type.bpp(1) as usize - * self.header.width as usize - * self.header.height as usize; - let mut data = vec![0; data_size]; - let mut encode_type = Vec::new(); - Self::decode_image( - &mut data, - &self.data, - self.header.width, - self.header.height, - self.color_type.bpp(1) as u8, - &mut encode_type, - )?; - if self.color_type.bpp(1) == 4 && self.header.mode != 1 { - let alpha_flip = if self.header.mode == 2 { 0 } else { 0xFF }; - for i in (0..data_size).step_by(4) { - let a = data[i]; - let b = data[i + 1]; - let g = data[i + 2]; - let r = data[i + 3]; - data[i] = b; - data[i + 1] = g; - data[i + 2] = r; - data[i + 3] = a ^ alpha_flip; + let width = usize::from(self.header.width); + let height = usize::from(self.header.height); + let mut img = match &self.data { + CrxImageData::RowEncoded(encoded) => { + let pixel_size = self.color_type.bpp(1) as usize; + let row_bytes = pixel_size + .checked_mul(width) + .ok_or_else(|| anyhow::anyhow!("Image row size overflow"))?; + let data_size = row_bytes + .checked_mul(height) + .ok_or_else(|| anyhow::anyhow!("Image buffer size overflow"))?; + let mut data = vec![0u8; data_size]; + let mut encode_type = Vec::with_capacity(height); + Self::decode_image( + &mut data, + encoded, + self.header.width, + self.header.height, + self.color_type.bpp(1) as u8, + &mut encode_type, + )?; + if self.color_type.bpp(1) == 4 && self.header.mode != 1 { + let alpha_flip = if self.header.mode == 2 { 0 } else { 0xFF }; + for chunk in data.chunks_mut(4) { + let a = chunk[0]; + let b = chunk[1]; + let g = chunk[2]; + let r = chunk[3]; + chunk[0] = b; + chunk[1] = g; + chunk[2] = r; + chunk[3] = a ^ alpha_flip; + } + } + ImageData { + width: self.header.width as u32, + height: self.header.height as u32, + depth: 8, + color_type: self.color_type, + data, + } + } + CrxImageData::Direct(pixels) => { + let mut data = pixels.clone(); + if self.color_type == ImageColorType::Bgra && self.header.mode != 1 { + let alpha_flip = if self.header.mode == 2 { 0 } else { 0xFF }; + for chunk in data.chunks_mut(4) { + let a = chunk[0]; + let b = chunk[1]; + let g = chunk[2]; + let r = chunk[3]; + chunk[0] = b; + chunk[1] = g; + chunk[2] = r; + chunk[3] = a ^ alpha_flip; + } + } + ImageData { + width: self.header.width as u32, + height: self.header.height as u32, + depth: 8, + color_type: self.color_type, + data, + } + } + CrxImageData::IndexedV1 { + pixels, + stride, + palette, + palette_format, + pixel_depth_bits, + } => { + let total_pixels = width + .checked_mul(height) + .ok_or_else(|| anyhow::anyhow!("Image dimensions overflow"))?; + let mut indexed = Vec::with_capacity(total_pixels); + for row in 0..height { + let start = row + .checked_mul(*stride) + .ok_or_else(|| anyhow::anyhow!("Row offset overflow"))?; + let end = start + .checked_add(width) + .ok_or_else(|| anyhow::anyhow!("Row slice overflow"))?; + if end > pixels.len() { + return Err(anyhow::anyhow!("CRX v1 indexed data is truncated")); + } + indexed.extend_from_slice(&pixels[start..end]); + } + let image = convert_index_palette_to_normal_bitmap( + &indexed, + *pixel_depth_bits, + palette, + *palette_format, + width, + height, + )?; + image } - } - let img = ImageData { - width: self.header.width as u32, - height: self.header.height as u32, - depth: 8, - color_type: self.color_type, - data, }; + if self.canvas { let (img_width, img_height) = if self.header.clips.is_empty() { (self.header.width as u32, self.header.height as u32) @@ -863,13 +1142,13 @@ impl Script for CrxImage { let clip = &self.header.clips[0]; (clip.img_width as u32, clip.img_height as u32) }; - return Ok(draw_on_canvas( + img = draw_on_canvas( img, img_width, img_height, self.header.inner_x as u32, self.header.inner_y as u32, - )?); + )?; } Ok(img) } @@ -933,6 +1212,9 @@ impl Script for CrxImage { ImageColorType::Bgra => 1, _ => return Err(anyhow::anyhow!("Unsupported color type: {:?}", color_type)), }; + if new_header.version == 1 { + new_header.version = 2; // Upgrade to version 2 + } new_header.flags |= 0x10; // Force add compressed data length let pixel_size = color_type.bpp(1) as u8; if color_type == ImageColorType::Bgra && self.header.mode != 1 { @@ -948,13 +1230,31 @@ impl Script for CrxImage { data.data[i + 3] = r; } } - let encoded = if self.row_type.is_origin() { + let encoded = if self.row_type.is_origin() && self.data.is_row_encoded() { let mut row_type = Vec::with_capacity(self.header.height as usize); - let row_len = self.header.width as usize * self.color_type.bpp(1) as usize + 1; - let mut cur_pos = 0; + let pixel_size_bytes = self.color_type.bpp(1) as usize; + let row_len = pixel_size_bytes + .checked_mul(self.header.width as usize) + .ok_or_else(|| anyhow::anyhow!("Row length overflow"))? + .checked_add(1) + .ok_or_else(|| anyhow::anyhow!("Row length overflow"))?; + let buffer = match &self.data { + CrxImageData::RowEncoded(buf) => buf, + _ => { + return Err(anyhow::anyhow!( + "Original row type information is unavailable" + )); + } + }; + let mut cur_pos = 0usize; for _ in 0..self.header.height { - row_type.push(self.data[cur_pos]); - cur_pos += row_len; + if cur_pos >= buffer.len() { + return Err(anyhow::anyhow!("Row type offset exceeds buffer length")); + } + row_type.push(buffer[cur_pos]); + cur_pos = cur_pos + .checked_add(row_len) + .ok_or_else(|| anyhow::anyhow!("Row type offset overflow"))?; } Self::encode_image_origin( &data.data, @@ -963,7 +1263,7 @@ impl Script for CrxImage { pixel_size, &row_type, )? - } else if self.row_type.is_best() { + } else if self.row_type.is_best() || (self.row_type.is_origin() && !self.data.is_row_encoded()){ Self::encode_image_best(&data.data, new_header.width, new_header.height, pixel_size)? } else if let CircusCrxMode::Fixed(mode) = self.row_type { Self::encode_image_fixed( diff --git a/src/utils/img.rs b/src/utils/img.rs index 06423db..0bfac0a 100644 --- a/src/utils/img.rs +++ b/src/utils/img.rs @@ -4,6 +4,7 @@ use super::jxl::*; use crate::ext::io::*; use crate::types::*; use anyhow::Result; +use std::convert::TryFrom; /// Reverses the alpha values of an image. /// @@ -586,3 +587,193 @@ pub fn draw_on_img_with_opacity( Ok(()) } + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum PaletteFormat { + /// R G B Color + Rgb, + /// B G R Color + Bgr, + /// R G B Color with a unused byte + RgbX, + /// B G R Color with a unused byte + BgrX, + /// R G B A Color + RgbA, + /// B G R A Color + BgrA, +} + +/// Converts indexed pixel data and a palette into a standard image buffer. +/// +/// `pixel_size` is expressed in bits per pixel for the indexed data. +pub fn convert_index_palette_to_normal_bitmap( + pixel_data: &[u8], + pixel_size: usize, + palettes: &[u8], + palette_format: PaletteFormat, + width: usize, + height: usize, +) -> Result { + if width == 0 || height == 0 { + return Err(anyhow::anyhow!("Image dimensions must be non-zero")); + } + if pixel_size == 0 { + return Err(anyhow::anyhow!("pixel_size must be greater than zero")); + } + + let width_u32 = + u32::try_from(width).map_err(|_| anyhow::anyhow!("width exceeds u32::MAX: {}", width))?; + let height_u32 = u32::try_from(height) + .map_err(|_| anyhow::anyhow!("height exceeds u32::MAX: {}", height))?; + + let pixel_count = width + .checked_mul(height) + .ok_or_else(|| anyhow::anyhow!("Image dimensions overflow: {}x{}", width, height))?; + + let palette_entry_size = match palette_format { + PaletteFormat::Rgb | PaletteFormat::Bgr => 3usize, + PaletteFormat::RgbX | PaletteFormat::BgrX | PaletteFormat::RgbA | PaletteFormat::BgrA => { + 4usize + } + }; + + if palettes.len() < palette_entry_size { + return Err(anyhow::anyhow!("Palette data is too small")); + } + if palettes.len() % palette_entry_size != 0 { + return Err(anyhow::anyhow!( + "Palette length {} is not a multiple of {}", + palettes.len(), + palette_entry_size + )); + } + let palette_color_count = palettes.len() / palette_entry_size; + if palette_color_count == 0 { + return Err(anyhow::anyhow!("Palette does not contain any colors")); + } + + let (color_type, output_channels) = match palette_format { + PaletteFormat::Rgb | PaletteFormat::RgbX => (ImageColorType::Rgb, 3usize), + PaletteFormat::Bgr | PaletteFormat::BgrX => (ImageColorType::Bgr, 3usize), + PaletteFormat::RgbA => (ImageColorType::Rgba, 4usize), + PaletteFormat::BgrA => (ImageColorType::Bgra, 4usize), + }; + + let palette_table_len = palette_color_count + .checked_mul(output_channels) + .ok_or_else(|| anyhow::anyhow!("Palette size overflow"))?; + let mut palette_table = Vec::with_capacity(palette_table_len); + for idx in 0..palette_color_count { + let base = idx * palette_entry_size; + match palette_format { + PaletteFormat::Rgb => { + palette_table.extend_from_slice(&palettes[base..base + 3]); + } + PaletteFormat::Bgr => { + palette_table.extend_from_slice(&palettes[base..base + 3]); + } + PaletteFormat::RgbX => { + palette_table.extend_from_slice(&palettes[base..base + 3]); + } + PaletteFormat::BgrX => { + palette_table.extend_from_slice(&palettes[base..base + 3]); + } + PaletteFormat::RgbA => { + palette_table.extend_from_slice(&palettes[base..base + 4]); + } + PaletteFormat::BgrA => { + palette_table.extend_from_slice(&palettes[base..base + 4]); + } + } + } + + let total_bits_required = pixel_count + .checked_mul(pixel_size) + .ok_or_else(|| anyhow::anyhow!("Pixel count overflow for pixel_size {}", pixel_size))?; + if total_bits_required > pixel_data.len() * 8 { + return Err(anyhow::anyhow!( + "Pixel data too short: need {} bits, have {} bits", + total_bits_required, + pixel_data.len() * 8 + )); + } + + let output_len = pixel_count + .checked_mul(output_channels) + .ok_or_else(|| anyhow::anyhow!("Output image size overflow"))?; + let mut output = Vec::with_capacity(output_len); + + let stride = output_channels; + if pixel_size == 8 { + if pixel_data.len() < pixel_count { + return Err(anyhow::anyhow!( + "Pixel data too short: expected {} bytes, got {}", + pixel_count, + pixel_data.len() + )); + } + for &index in pixel_data.iter().take(pixel_count) { + let idx = index as usize; + if idx >= palette_color_count { + return Err(anyhow::anyhow!( + "Palette index {} exceeds palette size {}", + idx, + palette_color_count + )); + } + let start = idx * stride; + output.extend_from_slice(&palette_table[start..start + stride]); + } + } else { + let mut bit_offset = 0usize; + for _ in 0..pixel_count { + let idx = read_bits_as_usize(pixel_data, bit_offset, pixel_size)?; + bit_offset = bit_offset + .checked_add(pixel_size) + .ok_or_else(|| anyhow::anyhow!("Bit offset overflow"))?; + if idx >= palette_color_count { + return Err(anyhow::anyhow!( + "Palette index {} exceeds palette size {}", + idx, + palette_color_count + )); + } + let start = idx * stride; + output.extend_from_slice(&palette_table[start..start + stride]); + } + } + + Ok(ImageData { + width: width_u32, + height: height_u32, + color_type, + depth: 8, + data: output, + }) +} + +fn read_bits_as_usize(data: &[u8], bit_offset: usize, bit_len: usize) -> Result { + if bit_len == 0 { + return Err(anyhow::anyhow!("bit_len must be greater than zero")); + } + if bit_len > (std::mem::size_of::() * 8) { + return Err(anyhow::anyhow!("Cannot read {} bits into usize", bit_len)); + } + + let mut value = 0usize; + for bit_idx in 0..bit_len { + let absolute_bit = bit_offset + bit_idx; + let byte_index = absolute_bit / 8; + if byte_index >= data.len() { + return Err(anyhow::anyhow!( + "Bit offset {} exceeds pixel data", + absolute_bit + )); + } + let bit_in_byte = 7 - (absolute_bit % 8); + let bit = (data[byte_index] >> bit_in_byte) & 1; + value = (value << 1) | bit as usize; + } + Ok(value) +}