diff --git a/.lock b/.lock new file mode 100644 index 0000000..e69de29 diff --git a/.nojekyll b/.nojekyll new file mode 100644 index 0000000..e69de29 diff --git a/crates.js b/crates.js new file mode 100644 index 0000000..c30c5e5 --- /dev/null +++ b/crates.js @@ -0,0 +1 @@ +window.ALL_CRATES = ["mpic"]; \ No newline at end of file diff --git a/help.html b/help.html new file mode 100644 index 0000000..6effb54 --- /dev/null +++ b/help.html @@ -0,0 +1 @@ +
pub struct MpicRgb666 { /* private fields */ }
source
. Read moreself
and other
values to be equal, and is used
+by ==
.self
and other
) and is used by the <=
+operator. Read morepub struct MpicRgb888 {
+ pub r: u8,
+ pub g: u8,
+ pub b: u8,
+}
r: u8
§g: u8
§b: u8
source
. Read moreself
and other
values to be equal, and is used
+by ==
.self
and other
) and is used by the <=
+operator. Read morepub struct MpicYuv666 {
+ pub y: u8,
+ pub u: u8,
+ pub v: u8,
+}
y: u8
§u: u8
§v: u8
source
. Read moreself
and other
values to be equal, and is used
+by ==
.self
and other
) and is used by the <=
+operator. Read morepub const PREFERRED_FILE_EXT: &str = "mpic";
Redirecting to ../../mpic/struct.Decoder.html...
+ + + \ No newline at end of file diff --git a/mpic/encode/struct.Encoder.html b/mpic/encode/struct.Encoder.html new file mode 100644 index 0000000..25215ce --- /dev/null +++ b/mpic/encode/struct.Encoder.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../mpic/struct.Encoder.html...
+ + + \ No newline at end of file diff --git a/mpic/enum.DecodeError.html b/mpic/enum.DecodeError.html new file mode 100644 index 0000000..aa6a091 --- /dev/null +++ b/mpic/enum.DecodeError.html @@ -0,0 +1,34 @@ +pub enum DecodeError {
+ InvalidInput,
+ InvalidData,
+}
source
. Read moreself
and other
values to be equal, and is used
+by ==
.self
and other
) and is used by the <=
+operator. Read morepub enum EncodeError {
+ InvalidInput,
+}
source
. Read moreself
and other
values to be equal, and is used
+by ==
.self
and other
) and is used by the <=
+operator. Read moremPic - Simple Lossy Compression Image Format for Embedded Platforms
+embedded-graphics
; add features = ["embedded"]
to Cargo.toml.no_std
, No alloc
is needed for decoding.pub struct Decoder<'a, T> { /* private fields */ }
pub struct Encoder;
#[repr(C, packed(1))]pub struct FileHeader { /* private fields */ }
pub struct ImageInfo { /* private fields */ }
self
and other
) and is used by the <=
+operator. Read moreU::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","","","Returns the argument unchanged.","","Returns the argument unchanged.","","","","","","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""],"i":[0,0,0,0,0,0,7,6,7,2,2,0,2,2,11,30,2,6,7,8,11,30,2,6,7,8,11,30,2,6,7,8,11,2,11,30,2,6,7,8,11,30,2,6,7,8,11,30,2,6,7,8,6,7,8,6,7,8,6,7,8,0,11,11,11,11,11,11,30,30,30,6,7,8,6,7,8,11,30,2,6,7,8,2,8,11,2,11,30,2,6,7,8,2,11,2,11,30,2,6,7,8,11,30,2,6,7,8,6,7,8,11,30,2,6,7,8,11,30,2,6,7,8,11,11,6,7,8,11,30,2,6,7,8,11,30,2,6,7,8,11,30,2,6,7,8,11,30,2,6,7,8,11,30,2,6,7,8,8,11,30,2,6,7,8,11,30,2,6,7,8,0,0,0,28,16,29,28,16,28,16,29,28,16,29,28,16,29,28,16,29,28,16,29,28,16,29,28,16,29,28,16,29,28,16,29,28,16,29,28,28,16,16,29,29,29,28,16,28,16,28,16,29,16,28,16,29,28,16,29,28,16,29,28,16,29,28,16,28,16,29,28,16,29,28,16,29,28,16,29,28,16,29,28,16,29,29,28,16,29,28,16,29,29,28,16,29,28,16,29,29],"f":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,1,[]],[2,[[4,[3]]]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[6,6],[7,7],[8,8],[[-1,-2],9,[],[]],[[-1,-2],9,[],[]],[[-1,-2],9,[],[]],[[6,6],10],[[7,7],10],[[8,8],10],0,[[[11,[-1]]],[[13,[[12,[3]],7]]],[]],[[[4,[3]]],[[13,[[9,[[14,[3]],[14,[3]],[14,[3]]]],7]]]],[[[11,[-1]]],[[13,[[12,[3]],7]]],[]],[[[11,[-1]],[4,[3]]],[[13,[9,7]]],[]],[[[11,[-1]],-2],[[13,[9]]],[15,[17,[16]]],18],[[[11,[-1]],-2,1],[[13,[9]]],[15,[17,[16]]],18],[[[4,[3]],19,19],[[13,[[12,[3]],6]]]],[[[14,[3]],[14,[3]],[14,[3]]],[[20,[3]]]],[[[4,[3]],19,19,-1],[[13,[9,6]]],21],[[6,6],22],[[7,7],22],[[8,8],22],[[6,23],24],[[7,23],24],[[8,23],24],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[[[4,[3]]],[[5,[2]]]],[8,19],[[[11,[-1]]],8,[]],[2,8],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[2,22],[[[4,[3]]],[[5,[[11,[-1]]]]],[]],[[19,19],[[5,[2]]]],[-1,[[9,[-2,22]]],[],[]],[-1,[[9,[-2,22]]],[],[]],[-1,[[9,[-2,22]]],[],[]],[-1,[[9,[-2,22]]],[],[]],[-1,[[9,[-2,22]]],[],[]],[-1,[[9,[-2,22]]],[],[]],[-1,[[9,[-2,22]]],[],[]],[-1,[[9,[-2,22]]],[],[]],[-1,[[9,[-2,22]]],[],[]],[-1,[[9,[-2,22]]],[],[]],[-1,[[9,[-2,22]]],[],[]],[-1,[[9,[-2,22]]],[],[]],[[6,6],[[5,[10]]]],[[7,7],[[5,[10]]]],[[8,8],[[5,[10]]]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[[[11,[-1]]],25,[]],[[-1,1],[[26,[-2]]],[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,[[13,[-2]]],[],[]],[-1,[[13,[-2]]],[],[]],[-1,[[13,[-2]]],[],[]],[-1,[[13,[-2]]],[],[]],[-1,[[13,[-2]]],[],[]],[-1,[[13,[-2]]],[],[]],[-1,[[13,[-2]]],[],[]],[-1,[[13,[-2]]],[],[]],[-1,[[13,[-2]]],[],[]],[-1,[[13,[-2]]],[],[]],[-1,[[13,[-2]]],[],[]],[-1,[[13,[-2]]],[],[]],[-1,27,[]],[-1,27,[]],[-1,27,[]],[-1,27,[]],[-1,27,[]],[-1,27,[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[8,19],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],0,0,0,[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],0,[16,3],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[28,28],[16,16],[29,29],[[-1,-2],9,[],[]],[[-1,-2],9,[],[]],[[-1,-2],9,[],[]],[[28,28],10],[[16,16],10],[[29,29],10],[[28,28],22],[[16,16],22],[[29,29],22],[[28,23],24],[[16,23],24],[[29,23],24],[-1,-1,[]],[16,28],[29,16],[-1,-1,[]],[28,29],[-1,-1,[]],[28,29],[29,28],[29,16],0,[16,3],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[16,28],[[3,3,3],28],[[3,3,3],16],[[3,3,3],29],[-1,[[9,[-2,22]]],[],[]],[-1,[[9,[-2,22]]],[],[]],[-1,[[9,[-2,22]]],[],[]],[-1,[[9,[-2,22]]],[],[]],[-1,[[9,[-2,22]]],[],[]],[-1,[[9,[-2,22]]],[],[]],[[28,28],[[5,[10]]]],[[16,16],[[5,[10]]]],[[29,29],[[5,[10]]]],0,[16,3],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,[[13,[-2]]],[],[]],[-1,[[13,[-2]]],[],[]],[-1,[[13,[-2]]],[],[]],[-1,[[13,[-2]]],[],[]],[-1,[[13,[-2]]],[],[]],[-1,[[13,[-2]]],[],[]],[-1,27,[]],[-1,27,[]],[-1,27,[]],0,[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],0,[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],0],"c":[],"p":[[3,"Rectangle",271],[3,"FileHeader",0],[15,"u8"],[15,"slice"],[4,"Option",272],[4,"EncodeError",0],[4,"DecodeError",0],[3,"ImageInfo",0],[15,"tuple"],[4,"Ordering",273],[3,"Decoder",0],[3,"Vec",274],[4,"Result",275],[15,"array"],[8,"PixelColor",276],[3,"MpicRgb666",171],[8,"From",277],[8,"DrawTarget",278],[15,"u32"],[3,"Vec",279],[8,"FnMut",280],[15,"bool"],[3,"Formatter",281],[6,"Result",281],[3,"Size",282],[3,"SubImage",283],[3,"TypeId",284],[3,"MpicRgb888",171],[3,"MpicYuv666",171],[3,"Encoder",0]],"b":[]}\
+}');
+if (typeof window !== 'undefined' && window.initSearch) {window.initSearch(searchIndex)};
+if (typeof exports !== 'undefined') {exports.searchIndex = searchIndex};
diff --git a/settings.html b/settings.html
new file mode 100644
index 0000000..4e9ac55
--- /dev/null
+++ b/settings.html
@@ -0,0 +1 @@
+1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +
use heapless::Vec;
+
+/// 8bit uncompressed chunk size
+pub const UNCOMPRESSED_SIZE: usize = 96;
+/// 6bit compacted chunk size
+pub const COMPACTED_SIZE: usize = 72;
+/// Theoretical Minimum Compressed Data: ANY VALUE + (SLIDE * 2) = 5
+pub const MINIMAL_COMPRESSED_SIZE: usize = 5;
+
+pub(crate) fn compress(src: &[u8; UNCOMPRESSED_SIZE], output: &mut Vec<u8, 128>) {
+ output.clear();
+
+ // Attempt LZ compression
+ let mut cursor = 0;
+ while let Some(current) = src.get(cursor) {
+ cursor += {
+ let min_short_len = 2;
+ let max_short_len = 3 + min_short_len;
+ let max_short_slide = 32;
+ let min_long_len = 3;
+ let max_long_len = 63 + min_long_len;
+ let max_slide = 64;
+ let mut matches = (0, 0);
+
+ for slide in 1..=cursor.min(max_slide) {
+ let len = check_len(src, cursor, cursor - slide).min(max_long_len);
+ if matches.0 < len {
+ matches = (len, slide);
+ }
+ }
+
+ let len = matches.0;
+ let encoded_slide = matches.1 as u8;
+ if len <= max_short_len && len >= min_short_len && encoded_slide <= max_short_slide {
+ output
+ .push(0x80 | (((len - min_short_len) as u8) << 5) | (encoded_slide - 1))
+ .unwrap();
+ len
+ } else if len >= min_long_len {
+ output.push(0x40 | (len - min_long_len) as u8).unwrap();
+ output.push(encoded_slide - 1).unwrap();
+ len
+ } else {
+ output.push(*current).unwrap();
+ 1
+ }
+ }
+ }
+
+ // If compression does not reduce size much, switch to compaction
+ if output.len() < COMPACTED_SIZE {
+ return;
+ }
+ output.clear();
+
+ // 6bit compaction
+ for chunk in src.chunks(4) {
+ let s1 = (chunk[0] & 0x3F) as u32;
+ let s2 = (chunk[1] & 0x3F) as u32;
+ let s3 = (chunk[2] & 0x3F) as u32;
+ let s4 = (chunk[3] & 0x3F) as u32;
+
+ let d0 = s1 | s2.wrapping_shl(6) | s3.wrapping_shl(12) | s4.wrapping_shl(18);
+
+ output.push(d0 as u8).unwrap();
+ output.push(d0.wrapping_shr(8) as u8).unwrap();
+ output.push(d0.wrapping_shr(16) as u8).unwrap();
+ }
+}
+
+pub(crate) fn decompress(src: &[u8], output: &mut Vec<u8, UNCOMPRESSED_SIZE>) -> Option<()> {
+ let len = src.len();
+ output.clear();
+ if len == UNCOMPRESSED_SIZE {
+ // 8bit uncompressed
+ output.extend_from_slice(src).ok()
+ } else if len == COMPACTED_SIZE {
+ // 6bit compacted
+ let mut src = src.iter();
+ for _ in 0..24 {
+ let b1 = *src.next()? as u32;
+ let b2 = *src.next()? as u32;
+ let b3 = *src.next()? as u32;
+ let d0 = b1 | b2.wrapping_shl(8) | b3.wrapping_shl(16);
+ output.push((d0 & 0x3F) as u8).ok()?;
+ output.push((d0.wrapping_shr(6) & 0x3F) as u8).ok()?;
+ output.push((d0.wrapping_shr(12) & 0x3F) as u8).ok()?;
+ output.push((d0.wrapping_shr(18) & 0x3F) as u8).ok()?;
+ }
+ Some(())
+ } else if is_valid_compressed_size(len) {
+ // compressed
+ let mut cursor = 0;
+ while cursor < len {
+ let data = *src.get(cursor)?;
+ match data {
+ 0b0000_0000..=0b0011_1111 => {
+ // 00vv_vvvv raw value
+ output.push(data & 0x3F).ok()?;
+ }
+ 0b0100_0000..=0b0111_1111 => {
+ // 01nn_nnnn 00mm_mmmm slide long
+ let slen = (data & 0x3F) as usize + 3;
+ let slide = *src.get(cursor + 1)?;
+ if (slide & 0xC0) != 0 {
+ // RESERVED
+ return None;
+ }
+ let slide = slide as usize + 1;
+ if output.len() < slide || output.len() + slen > UNCOMPRESSED_SIZE {
+ return None;
+ }
+ let base = output.len() - slide;
+ for i in 0..slen {
+ let v = *output.get(base + i)?;
+ output.push(v).ok()?;
+ }
+ cursor += 1;
+ }
+ 0b1000_0000..=0b1111_1111 => {
+ // 1nnm_mmmm slide short
+ let slen = 2 + ((data & 0x60) as usize >> 5);
+ let slide = (data & 0x1F) as usize + 1;
+ if output.len() < slide || output.len() + slen > UNCOMPRESSED_SIZE {
+ return None;
+ }
+ let base = output.len() - slide;
+ for i in 0..slen {
+ let v = *output.get(base + i)?;
+ output.push(v).ok()?;
+ }
+ }
+ }
+ cursor += 1;
+ }
+ (output.len() == UNCOMPRESSED_SIZE).then(|| ())
+ } else {
+ // reserved
+ None
+ }
+}
+
+#[inline]
+pub(crate) fn is_valid_compressed_size(size: usize) -> bool {
+ size >= MINIMAL_COMPRESSED_SIZE && size < COMPACTED_SIZE
+}
+
+#[inline]
+fn check_len(src: &[u8], lhs: usize, rhs: usize) -> usize {
+ let mut len = 0;
+ loop {
+ let lhs = match src.get(lhs + len) {
+ Some(v) => *v,
+ None => return len,
+ };
+ let rhs = match src.get(rhs + len) {
+ Some(v) => *v,
+ None => return len,
+ };
+ if lhs != rhs {
+ return len;
+ }
+ len += 1;
+ }
+}
+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +192 +193 +194 +195 +196 +197 +198 +199 +200 +201 +202 +203 +204 +205 +206 +207 +208 +209 +210 +211 +212 +213 +214 +215 +216 +217 +218 +219 +220 +221 +222 +223 +224 +225 +226 +227 +228 +229 +230 +231 +232 +233 +234 +235 +236 +237 +238 +239 +240 +241 +242 +243 +244 +245 +246 +247 +248 +249 +250 +251 +252 +253 +254 +255 +256 +257 +258 +259 +260 +261 +262 +263 +264 +265 +266 +267 +268 +269 +270 +271 +272 +
#[cfg(feature = "embedded")]
+use embedded_graphics::pixelcolor::*;
+
+#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
+pub struct MpicRgb888 {
+ pub r: u8,
+ pub g: u8,
+ pub b: u8,
+}
+
+#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
+pub struct MpicRgb666 {
+ r: u8,
+ g: u8,
+ b: u8,
+}
+
+#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
+pub struct MpicYuv666 {
+ pub y: u8,
+ pub u: u8,
+ pub v: u8,
+}
+
+impl MpicRgb888 {
+ #[inline]
+ pub const fn new(r: u8, g: u8, b: u8) -> Self {
+ Self { r, g, b }
+ }
+
+ #[inline]
+ pub fn from_yuv(yuv: MpicYuv666) -> Self {
+ MpicRgb666::from_yuv(yuv).into_rgb888()
+ }
+}
+
+impl MpicRgb666 {
+ #[inline]
+ pub const fn new(r: u8, g: u8, b: u8) -> Self {
+ Self { r, g, b }
+ }
+
+ #[inline]
+ pub fn from_yuv(yuv: MpicYuv666) -> Self {
+ let y = u6_to_u8(yuv.y.wrapping_sub(4)) as i32;
+ let u = (u6_to_u8(yuv.u) as i32).wrapping_sub(128);
+ let v = (u6_to_u8(yuv.v) as i32).wrapping_sub(128);
+
+ let r = ((298 * y + 409 * v + 128).wrapping_shr(10)).max(0).min(63);
+ let g = ((298 * y - 100 * u - 208 * v + 128).wrapping_shr(10))
+ .max(0)
+ .min(63);
+ let b = ((298 * y + 516 * u + 128).wrapping_shr(10)).max(0).min(63);
+
+ Self {
+ r: r as u8,
+ g: g as u8,
+ b: b as u8,
+ }
+ }
+
+ #[inline]
+ pub const fn into_rgb888(self) -> MpicRgb888 {
+ MpicRgb888 {
+ r: self.r8(),
+ g: self.g8(),
+ b: self.b8(),
+ }
+ }
+
+ #[inline]
+ pub const fn r8(&self) -> u8 {
+ u6_to_u8(self.r)
+ }
+
+ #[inline]
+ pub const fn g8(&self) -> u8 {
+ u6_to_u8(self.g)
+ }
+
+ #[inline]
+ pub const fn b8(&self) -> u8 {
+ u6_to_u8(self.b)
+ }
+}
+
+impl MpicYuv666 {
+ #[inline]
+ pub const fn new(y: u8, u: u8, v: u8) -> Self {
+ Self { y, u, v }
+ }
+
+ #[inline]
+ pub const fn from_rgb(rgb: MpicRgb888) -> Self {
+ let r = rgb.r as i32;
+ let g = rgb.g as i32;
+ let b = rgb.b as i32;
+
+ let y = ((66 * r + 129 * g + 25 * b + 128).wrapping_shr(10)).wrapping_add(4);
+ let u = ((-38 * r - 74 * g + 112 * b + 128) / 256) + 128;
+ let v = ((112 * r - 94 * g - 18 * b + 128) / 256) + 128;
+
+ Self {
+ y: y as u8,
+ u: u.wrapping_shr(2) as u8,
+ v: v.wrapping_shr(2) as u8,
+ }
+ }
+}
+
+impl From<MpicRgb888> for MpicYuv666 {
+ #[inline]
+ fn from(rgb: MpicRgb888) -> Self {
+ Self::from_rgb(rgb)
+ }
+}
+
+impl From<MpicYuv666> for MpicRgb666 {
+ #[inline]
+ fn from(yuv: MpicYuv666) -> Self {
+ Self::from_yuv(yuv)
+ }
+}
+
+impl From<MpicRgb666> for MpicRgb888 {
+ #[inline]
+ fn from(value: MpicRgb666) -> Self {
+ value.into_rgb888()
+ }
+}
+
+macro_rules! from_rgb {
+ ($ident:ident, $shift_r:expr, $shift_g:expr, $shift_b:expr) => {
+ #[cfg(feature = "embedded")]
+ impl From<MpicRgb666> for $ident {
+ #[inline]
+ fn from(rgb: MpicRgb666) -> Self {
+ Self::new(
+ rgb.r.wrapping_shr($shift_r - 2),
+ rgb.g.wrapping_shr($shift_g - 2),
+ rgb.b.wrapping_shr($shift_b - 2),
+ )
+ }
+ }
+ };
+ ($ident:ident) => {
+ #[cfg(feature = "embedded")]
+ impl From<MpicRgb666> for $ident {
+ #[inline]
+ fn from(rgb: MpicRgb666) -> Self {
+ Self::new(u6_to_u8(rgb.r), u6_to_u8(rgb.g), u6_to_u8(rgb.b))
+ }
+ }
+ };
+}
+
+from_rgb!(Rgb555, 3, 3, 3);
+from_rgb!(Bgr555, 3, 3, 3);
+from_rgb!(Rgb565, 3, 2, 3);
+from_rgb!(Bgr565, 3, 2, 3);
+from_rgb!(Rgb666, 2, 2, 2);
+from_rgb!(Bgr666, 2, 2, 2);
+from_rgb!(Rgb888);
+from_rgb!(Bgr888);
+
+/// expand 6bit value to 8bit
+#[inline]
+#[allow(dead_code)]
+pub(crate) const fn u6_to_u8(val: u8) -> u8 {
+ let val = val.wrapping_shl(2);
+ val | val.wrapping_shr(6)
+}
+
+/// expand 4bit value to 8bit
+#[inline]
+#[allow(dead_code)]
+pub(crate) const fn u4_to_u8(val: u8) -> u8 {
+ val | val.wrapping_shl(4)
+}
+
+#[test]
+fn rgb_yuv() {
+ let allowed_rgb_error = 12;
+ let allowed_yuv_error = 1;
+
+ let mut max_y = u8::MIN;
+ let mut min_y = u8::MAX;
+ let mut max_u = u8::MIN;
+ let mut min_u = u8::MAX;
+ let mut max_v = u8::MIN;
+ let mut min_v = u8::MAX;
+
+ fn rgb_error(lhs: MpicRgb888, rhs: MpicRgb888) -> isize {
+ let l1 = lhs.r as isize;
+ let l2 = lhs.g as isize;
+ let l3 = lhs.b as isize;
+ let r1 = rhs.r as isize;
+ let r2 = rhs.g as isize;
+ let r3 = rhs.b as isize;
+
+ let diff1 = (l1 - r1).abs();
+ let diff2 = (l2 - r2).abs();
+ let diff3 = (l3 - r3).abs();
+
+ diff1.max(diff2).max(diff3)
+ }
+
+ fn yuv_error(lhs: MpicYuv666, rhs: MpicYuv666) -> isize {
+ let l1 = lhs.y as isize;
+ let l2 = lhs.u as isize;
+ let l3 = lhs.v as isize;
+ let r1 = rhs.y as isize;
+ let r2 = rhs.u as isize;
+ let r3 = rhs.v as isize;
+
+ let diff1 = (l1 - r1).abs();
+ let diff2 = (l2 - r2).abs();
+ let diff3 = (l3 - r3).abs();
+
+ diff1.max(diff2).max(diff3)
+ }
+
+ for r in 0..64 {
+ for g in 0..64 {
+ for b in 0..64 {
+ let r = u6_to_u8(r);
+ let g = u6_to_u8(g);
+ let b = u6_to_u8(b);
+ let rgb = MpicRgb888 { r, g, b };
+ let yuv = MpicYuv666::from_rgb(rgb);
+ max_y = max_y.max(yuv.y);
+ min_y = min_y.min(yuv.y);
+ max_u = max_u.max(yuv.u);
+ min_u = min_u.min(yuv.u);
+ max_v = max_v.max(yuv.v);
+ min_v = min_v.min(yuv.v);
+
+ let rgb2 = MpicRgb888::from_yuv(yuv);
+ let yuv2 = MpicYuv666::from_rgb(rgb2);
+
+ let rgb_error = rgb_error(rgb, rgb2);
+ let yuv_error = yuv_error(yuv, yuv2);
+
+ assert!(
+ rgb_error <= allowed_rgb_error,
+ "RGB Error exceeded limit: {} > {} RGB {:?} => YUV {:?} => {:?} ",
+ rgb_error,
+ allowed_rgb_error,
+ (rgb.r, rgb.g, rgb.b),
+ (yuv.y, yuv.u, yuv.v),
+ (rgb2.r, rgb2.g, rgb2.b),
+ );
+
+ assert!(
+ yuv_error <= allowed_yuv_error,
+ "YUV Error exceeded limit: {} > {} RGB {:?} => YUV {:?} => {:?} => {:?}",
+ yuv_error,
+ allowed_yuv_error,
+ (rgb.r, rgb.g, rgb.b),
+ (yuv.y, yuv.u, yuv.v),
+ (rgb2.r, rgb2.g, rgb2.b),
+ (yuv2.y, yuv2.u, yuv2.v),
+ );
+ }
+ }
+ }
+
+ assert_eq!(
+ (min_y, min_u, min_v, max_y, max_u, max_v),
+ (4, 4, 4, 58, 60, 60)
+ );
+}
+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +192 +193 +194 +195 +196 +197 +198 +199 +200 +201 +202 +203 +204 +205 +206 +207 +208 +209 +210 +211 +212 +213 +214 +215 +216 +217 +218 +219 +220 +221 +222 +223 +224 +225 +226 +227 +228 +229 +230 +231 +232 +233 +234 +235 +236 +237 +238 +239 +240 +241 +242 +243 +244 +245 +246 +247 +248 +249 +250 +251 +252 +253 +254 +255 +256 +257 +258 +259 +260 +261 +262 +263 +264 +265 +266 +267 +268 +269 +270 +271 +272 +273 +274 +275 +276 +277 +278 +279 +280 +281 +282 +283 +284 +285 +286 +287 +288 +289 +290 +291 +292 +293 +294 +295 +296 +297 +298 +299 +300 +301 +302 +303 +304 +305 +306 +307 +308 +309 +310 +311 +312 +313 +314 +315 +316 +317 +318 +319 +320 +321 +322 +323 +324 +325 +326 +327 +328 +329 +330 +331 +332 +333 +334 +335 +336 +337 +338 +339 +340 +341 +342 +343 +344 +345 +346 +347 +348 +349 +350 +351 +352 +353 +354 +355 +356 +357 +358 +359 +360 +361 +362 +363 +364 +365 +366 +367 +368 +369 +370 +371 +372 +373 +374 +375 +376 +377 +378 +379 +380 +381 +382 +383 +384 +385 +386 +387 +388 +389 +390 +391 +392 +393 +394 +395 +396 +397 +398 +399 +400 +
use crate::{chunk::UNCOMPRESSED_SIZE, color::*, *};
+use core::marker::PhantomData;
+use heapless::Vec;
+
+#[cfg(feature = "embedded")]
+use embedded_graphics::{prelude::*, primitives::Rectangle};
+
+pub struct Decoder<'a, T> {
+ blob: &'a [u8],
+ info: ImageInfo,
+ _phantom: PhantomData<T>,
+}
+
+impl<'a, T> Decoder<'a, T> {
+ #[inline]
+ pub fn new(blob: &'a [u8]) -> Option<Self> {
+ let header = FileHeader::from_bytes(blob)?;
+ if !header.is_valid() {
+ return None;
+ }
+ let info = header.info();
+ Some(Self {
+ blob,
+ info,
+ _phantom: PhantomData,
+ })
+ }
+
+ #[inline]
+ pub fn info(&self) -> ImageInfo {
+ self.info
+ }
+
+ #[cfg(feature = "alloc")]
+ pub fn decode(&self) -> Result<alloc::vec::Vec<u8>, DecodeError> {
+ let width = self.info().width() as usize;
+ let height = self.info().height() as usize;
+ let vec_size = width * height * 3;
+ let mut vec = alloc::vec::Vec::with_capacity(vec_size);
+ vec.resize(vec_size, 0);
+ self.decode_to_slice(vec.as_mut()).map(|_| vec)
+ }
+
+ pub fn decode_to_slice(&self, output: &mut [u8]) -> Result<(), DecodeError> {
+ let width = self.info().width() as usize;
+ let height = self.info().height() as usize;
+ let vec_size = width * height * 3;
+ if output.len() < vec_size {
+ return Err(DecodeError::InvalidInput);
+ }
+
+ let w8 = width & !7;
+ let h8 = height & !7;
+ let w7 = width & 7;
+ let h7 = height & 7;
+
+ let mut cursor = size_of::<FileHeader>();
+ for y8 in (0..h8).step_by(8) {
+ for x8 in (0..w8).step_by(8) {
+ let len = *self.blob.get(cursor).ok_or(DecodeError::InvalidData)? as usize;
+ let src = self
+ .blob
+ .get(cursor + 1..cursor + len + 1)
+ .ok_or(DecodeError::InvalidData)?;
+ Self::decode_chunk(src).map(|(buf_y, buf_u, buf_v)| {
+ for y7 in 0..8 {
+ for x7 in 0..8 {
+ let y = buf_y[(y7 * 8 + x7) as usize];
+ let u = buf_u[(y7 * 8 + x7) as usize];
+ let v = buf_v[(y7 * 8 + x7) as usize];
+ let rgb = MpicRgb666::from_yuv(MpicYuv666::new(y, u, v));
+
+ let index = (x8 + x7 + (y8 + y7) * width) * 3;
+ output[index] = rgb.r8();
+ output[index + 1] = rgb.g8();
+ output[index + 2] = rgb.b8();
+ }
+ }
+ })?;
+ cursor += len + 1;
+ }
+ if w7 > 0 {
+ cursor += self._decode_edge(output, cursor, width, w8, y8, w7, 8)?;
+ }
+ }
+ if h7 > 0 {
+ for x8 in (0..w8).step_by(8) {
+ cursor += self._decode_edge(output, cursor, width, x8, w8, 8, h7)?;
+ }
+ if w7 > 0 {
+ self._decode_edge(output, cursor, width, w8, h8, w7, h7)?;
+ }
+ }
+ Ok(())
+ }
+
+ #[inline]
+ fn _decode_edge(
+ &self,
+ output: &mut [u8],
+ cursor: usize,
+ width: usize,
+ x8: usize,
+ y8: usize,
+ w7: usize,
+ h7: usize,
+ ) -> Result<usize, DecodeError> {
+ let len = *self.blob.get(cursor).ok_or(DecodeError::InvalidData)? as usize;
+ let src = self
+ .blob
+ .get(cursor + 1..cursor + len + 1)
+ .ok_or(DecodeError::InvalidData)?;
+ Self::decode_chunk(src).map(|(buf_y, buf_u, buf_v)| {
+ for y7 in 0..h7 {
+ for x7 in 0..w7 {
+ let y = buf_y[(y7 * 8 + x7) as usize];
+ let u = buf_u[(y7 * 8 + x7) as usize];
+ let v = buf_v[(y7 * 8 + x7) as usize];
+ let rgb = MpicRgb666::from_yuv(MpicYuv666::new(y, u, v));
+
+ let index = (x8 + x7 + (y8 + y7) * width) * 3;
+ output[index] = rgb.r8();
+ output[index + 1] = rgb.g8();
+ output[index + 2] = rgb.b8();
+ }
+ }
+ })?;
+
+ Ok(len + 1)
+ }
+
+ #[cfg(feature = "alloc")]
+ pub fn decode_rgba(&self) -> Result<alloc::vec::Vec<u8>, DecodeError> {
+ let width = self.info().width() as usize;
+ let height = self.info().height() as usize;
+ let vec_size = width * height * 4;
+ let mut vec = alloc::vec::Vec::with_capacity(vec_size);
+ vec.resize(vec_size, 0);
+
+ let w8 = width & !7;
+ let h8 = height & !7;
+ let w7 = width & 7;
+ let h7 = height & 7;
+
+ let mut cursor = size_of::<FileHeader>();
+ for y8 in (0..h8).step_by(8) {
+ for x8 in (0..w8).step_by(8) {
+ let len = *self.blob.get(cursor).ok_or(DecodeError::InvalidData)? as usize;
+ let src = self
+ .blob
+ .get(cursor + 1..cursor + len + 1)
+ .ok_or(DecodeError::InvalidData)?;
+ Self::decode_chunk(src).map(|(buf_y, buf_u, buf_v)| {
+ for y7 in 0..8 {
+ for x7 in 0..8 {
+ let y = buf_y[(y7 * 8 + x7) as usize];
+ let u = buf_u[(y7 * 8 + x7) as usize];
+ let v = buf_v[(y7 * 8 + x7) as usize];
+ let rgb = MpicRgb666::from_yuv(MpicYuv666::new(y, u, v));
+
+ let index = (x8 + x7 + (y8 + y7) * width) * 4;
+ vec[index] = rgb.r8();
+ vec[index + 1] = rgb.g8();
+ vec[index + 2] = rgb.b8();
+ vec[index + 3] = u8::MAX;
+ }
+ }
+ })?;
+ cursor += len + 1;
+ }
+ if w7 > 0 {
+ cursor +=
+ self._decode_edge_rgba(vec.as_mut_slice(), cursor, width, w8, y8, w7, 8)?;
+ }
+ }
+ if h7 > 0 {
+ for x8 in (0..w8).step_by(8) {
+ cursor +=
+ self._decode_edge_rgba(vec.as_mut_slice(), cursor, width, x8, h8, 8, h7)?;
+ }
+ if w7 > 0 {
+ self._decode_edge_rgba(vec.as_mut_slice(), cursor, width, w8, h8, w7, h7)?;
+ }
+ }
+
+ Ok(vec)
+ }
+
+ fn _decode_edge_rgba(
+ &self,
+ output: &mut [u8],
+ cursor: usize,
+ width: usize,
+ x8: usize,
+ y8: usize,
+ w7: usize,
+ h7: usize,
+ ) -> Result<usize, DecodeError> {
+ let len = *self.blob.get(cursor).ok_or(DecodeError::InvalidData)? as usize;
+ let src = self
+ .blob
+ .get(cursor + 1..cursor + len + 1)
+ .ok_or(DecodeError::InvalidData)?;
+ Self::decode_chunk(src).map(|(buf_y, buf_u, buf_v)| {
+ for y7 in 0..h7 {
+ for x7 in 0..w7 {
+ let y = buf_y[(y7 * 8 + x7) as usize];
+ let u = buf_u[(y7 * 8 + x7) as usize];
+ let v = buf_v[(y7 * 8 + x7) as usize];
+ let rgb = MpicRgb666::from_yuv(MpicYuv666::new(y, u, v));
+
+ let index = (x8 + x7 + (y8 + y7) * width) * 4;
+ output[index] = rgb.r8();
+ output[index + 1] = rgb.g8();
+ output[index + 2] = rgb.b8();
+ output[index + 3] = u8::MAX;
+ }
+ }
+ })?;
+
+ Ok(len + 1)
+ }
+
+ #[allow(dead_code)]
+ fn decode_sub_image<F, E>(
+ &self,
+ left: i32,
+ top: i32,
+ width: u32,
+ height: u32,
+ mut draw_block: F,
+ ) -> Result<(), E>
+ where
+ F: FnMut(u32, u32, u32, u32, &[u8; 64], &[u8; 64], &[u8; 64]) -> Result<(), E>,
+ {
+ let mut cursor = size_of::<FileHeader>();
+ let image_width = self.info().width();
+ let image_height = self.info().height();
+
+ let mut left = left;
+ let mut top = top;
+ let mut right = left + width as i32;
+ let mut bottom = top + height as i32;
+ if left < 0 {
+ right += left;
+ left = 0;
+ }
+ if top < 0 {
+ bottom += top;
+ top = 0;
+ }
+ let right = image_width.min(right as u32);
+ let bottom = image_height.min(bottom as u32);
+ let w8 = right & !7;
+ let h8 = bottom & !7;
+ let block_right = ceil_8(right);
+ let block_bottom = ceil_8(bottom);
+ let block_left = left as u32 & !7;
+ let block_top = top as u32 & !7;
+
+ let mut result = Ok(());
+ for y8 in (0..block_bottom).step_by(8) {
+ let h7 = if y8 == h8 { bottom & 7 } else { 8 };
+ for x8 in (0..image_width).step_by(8) {
+ let len = match self.blob.get(cursor) {
+ Some(v) => *v as usize,
+ None => return result,
+ };
+ if y8 >= block_top && x8 >= block_left && x8 <= block_right {
+ let src = match self.blob.get(cursor + 1..cursor + len + 1) {
+ Some(v) => v,
+ None => return result,
+ };
+ let w7 = if x8 == w8 { right & 7 } else { 8 };
+ match Self::decode_chunk(src).map(|(buf_y, buf_u, buf_v)| {
+ match draw_block(x8, y8, w7, h7, &buf_y, &buf_u, &buf_v) {
+ Ok(_) => (),
+ Err(e) => result = Err(e),
+ }
+ }) {
+ Ok(_) => (),
+ Err(_) => break,
+ }
+ if result.is_err() {
+ break;
+ }
+ }
+ cursor += len + 1;
+ }
+ }
+ result
+ }
+
+ pub fn decode_chunk(src: &[u8]) -> Result<([u8; 64], [u8; 64], [u8; 64]), DecodeError> {
+ let mut vec = Vec::<u8, UNCOMPRESSED_SIZE>::new();
+ chunk::decompress(src, &mut vec).ok_or(DecodeError::InvalidData)?;
+
+ let buf_y: &[u8; 64] = &vec[0..64]
+ .try_into()
+ .map_err(|_| DecodeError::InvalidData)?;
+
+ let buf_u = demosaic_uv(
+ &vec[64..80]
+ .try_into()
+ .map_err(|_| DecodeError::InvalidData)?,
+ );
+
+ let buf_v = demosaic_uv(
+ &vec[80..96]
+ .try_into()
+ .map_err(|_| DecodeError::InvalidData)?,
+ );
+
+ Ok((*buf_y, buf_u, buf_v))
+ }
+}
+
+#[cfg(feature = "embedded")]
+impl<T> OriginDimensions for Decoder<'_, T> {
+ #[inline]
+ fn size(&self) -> Size {
+ self.info().into()
+ }
+}
+
+#[cfg(feature = "embedded")]
+impl<T: PixelColor + From<MpicRgb666>> ImageDrawable for Decoder<'_, T> {
+ type Color = T;
+
+ fn draw<D>(&self, target: &mut D) -> Result<(), D::Error>
+ where
+ D: DrawTarget<Color = Self::Color>,
+ {
+ let rect = target.bounding_box();
+ self.decode_sub_image(
+ rect.top_left.x,
+ rect.top_left.y,
+ rect.size.width,
+ rect.size.height,
+ |x8, y8, w7, h7, buf_y, buf_u, buf_v| {
+ let mut colors = heapless::Vec::<T, 64>::new();
+ if w7 == 8 && h7 == 8 {
+ for index in 0..64 {
+ let rgb = MpicRgb666::from(MpicYuv666::new(
+ buf_y[index],
+ buf_u[index],
+ buf_v[index],
+ ));
+ let _ = colors.push(rgb.into());
+ }
+ } else {
+ for y7 in 0..h7 {
+ for x7 in 0..w7 {
+ let index = (y7 * 8 + x7) as usize;
+ let rgb = MpicRgb666::from(MpicYuv666::new(
+ buf_y[index],
+ buf_u[index],
+ buf_v[index],
+ ));
+ let _ = colors.push(rgb.into());
+ }
+ }
+ }
+ target.fill_contiguous(
+ &Rectangle::new(Point::new(x8 as i32, y8 as i32), Size::new(w7, h7)),
+ colors,
+ )
+ },
+ )
+ }
+
+ fn draw_sub_image<D>(&self, target: &mut D, area: &Rectangle) -> Result<(), D::Error>
+ where
+ D: DrawTarget<Color = Self::Color>,
+ {
+ self.draw(&mut target.translated(-area.top_left).clipped(area))
+ }
+}
+
+/// Unmosaic the U and V channels
+#[inline]
+pub(crate) fn demosaic_uv(data: &[u8; 16]) -> [u8; 64] {
+ let mut buf = [0u8; 64];
+ for y in 0..4 {
+ for x in 0..4 {
+ let base = y * 16 + x * 2;
+ let p = data[y * 4 + x];
+ buf[base] = p;
+ buf[base + 1] = p;
+ buf[base + 8] = p;
+ buf[base + 9] = p;
+ }
+ }
+ buf
+}
+
+#[inline(always)]
+const fn ceil_8(v: u32) -> u32 {
+ (v + 7) & !7
+}
+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +192 +193 +194 +195 +196 +197 +198 +199 +200 +201 +202 +203 +
use crate::{chunk::UNCOMPRESSED_SIZE, color::*, *};
+use heapless::Vec;
+
+pub struct Encoder;
+
+impl Encoder {
+ #[cfg(feature = "alloc")]
+ pub fn encode(
+ data: &[u8],
+ width: u32,
+ height: u32,
+ ) -> Result<alloc::vec::Vec<u8>, EncodeError> {
+ let mut vec = alloc::vec::Vec::new();
+ Self::encode_to_writer(data, width, height, |v| vec.extend_from_slice(v)).map(|_| vec)
+ }
+
+ pub fn encode_to_writer<F>(
+ data: &[u8],
+ width: u32,
+ height: u32,
+ mut writer: F,
+ ) -> Result<(), EncodeError>
+ where
+ F: FnMut(&[u8]),
+ {
+ if data.len() < (width as usize * height as usize * 3) {
+ return Err(EncodeError::InvalidInput);
+ }
+ let header = FileHeader::new(width, height).ok_or(EncodeError::InvalidInput)?;
+ writer(header.bytes());
+
+ let w8 = width & !7;
+ let h8 = height & !7;
+ let w7 = width & 7;
+ let h7 = height & 7;
+
+ for y8 in (0..h8).step_by(8) {
+ for x8 in (0..w8).step_by(8) {
+ let mut buf_y = [0u8; 64];
+ let mut buf_u = [0u8; 64];
+ let mut buf_v = [0u8; 64];
+ for index in 0..64 {
+ let x7 = index & 7;
+ let y7 = index >> 3;
+ let offset = ((y8 as usize + y7) * width as usize + x8 as usize + x7) * 3;
+
+ let r = data[offset + 0];
+ let g = data[offset + 1];
+ let b = data[offset + 2];
+ let yuv = MpicYuv666::from_rgb(MpicRgb888::new(r, g, b));
+ buf_y[index] = yuv.y;
+ buf_u[index] = yuv.u;
+ buf_v[index] = yuv.v;
+ }
+ let block = Self::encode_chunk(&buf_y, &buf_u, &buf_v);
+ writer(&[block.len() as u8]);
+ writer(block.as_slice());
+ }
+ if w7 > 0 {
+ let block = Self::_encode_edge(data, width, w8, y8, w7, 8);
+ writer(&[block.len() as u8]);
+ writer(block.as_slice());
+ }
+ }
+ if h7 > 0 {
+ for x8 in (0..w8).step_by(8) {
+ let block = Self::_encode_edge(data, width, x8, h8, 8, h7);
+ writer(&[block.len() as u8]);
+ writer(block.as_slice());
+ }
+ if w7 > 0 {
+ let block = Self::_encode_edge(data, width, w8, h8, w7, h7);
+ writer(&[block.len() as u8]);
+ writer(block.as_slice());
+ }
+ }
+
+ Ok(())
+ }
+
+ #[inline]
+ fn _encode_edge(data: &[u8], width: u32, x8: u32, y8: u32, w7: u32, h7: u32) -> Vec<u8, 128> {
+ let w7 = w7 as usize;
+ let h7 = h7 as usize;
+ assert!(w7 > 0 && w7 <= 8);
+ assert!(h7 > 0 && h7 <= 8);
+
+ let w1 = w7 & 1;
+ let h1 = h7 & 1;
+
+ let mut buf_y = [0; 64];
+ let mut buf_u = [0; 64];
+ let mut buf_v = [0; 64];
+ for y7 in 0..h7 {
+ for x7 in 0..w7 {
+ let index = y7 * 8 + x7;
+ let offset = ((y8 as usize + y7) * width as usize + x8 as usize + x7) * 3;
+ let r = data[offset + 0];
+ let g = data[offset + 1];
+ let b = data[offset + 2];
+ let yuv = MpicYuv666::from_rgb(MpicRgb888::new(r, g, b));
+ buf_y[index] = yuv.y;
+ buf_u[index] = yuv.u;
+ buf_v[index] = yuv.v;
+ }
+ if w1 > 0 {
+ let index = y7 * 8 + w7;
+ buf_y[index] = buf_y[index - 1];
+ buf_u[index] = buf_u[index - 1];
+ buf_v[index] = buf_v[index - 1];
+ }
+ for x7 in w7 + w1..8 {
+ let index = y7 * 8 + x7;
+ buf_y[index] = buf_y[0];
+ buf_u[index] = buf_u[0];
+ buf_v[index] = buf_v[0];
+ }
+ }
+ if h1 > 0 {
+ for x7 in 0..8 {
+ let index_l = h7 * 8 + x7;
+ let index_r = index_l - 8;
+ buf_y[index_l] = buf_y[index_r];
+ buf_u[index_l] = buf_u[index_r];
+ buf_v[index_l] = buf_v[index_r];
+ }
+ }
+ for y7 in h7 + h1..8 {
+ for x7 in 0..8 {
+ let index = y7 * 8 + x7;
+ buf_y[index] = buf_y[0];
+ buf_u[index] = buf_u[0];
+ buf_v[index] = buf_v[0];
+ }
+ }
+
+ Self::encode_chunk(&buf_y, &buf_u, &buf_v)
+ }
+
+ pub fn encode_chunk(buf_y: &[u8; 64], buf_u: &[u8; 64], buf_v: &[u8; 64]) -> Vec<u8, 128> {
+ let mut buf = [0; UNCOMPRESSED_SIZE];
+ for i in 0..64 {
+ buf[i] = buf_y[i];
+ }
+
+ let (buf_u, buf_v) = mosaic_uv(buf_u, buf_v);
+
+ for i in 0..16 {
+ buf[64 + i] = buf_u[i];
+ }
+ for i in 0..16 {
+ buf[80 + i] = buf_v[i];
+ }
+
+ let mut vec = Vec::<u8, 128>::new();
+ chunk::compress(&buf, &mut vec);
+ // vec.extend_from_slice(&buf).unwrap();
+
+ #[cfg(test)]
+ {
+ let mut unpacked = Vec::<u8, UNCOMPRESSED_SIZE>::new();
+ let result = chunk::decompress(vec.as_slice(), &mut unpacked);
+ if result.is_none() || unpacked.as_slice() != buf.as_slice() {
+ panic!(
+ "DECODE FAILED.\nEXPECTED:\n{:02x?}\nPACKED:\n{:02x?}\nUNPACKED:\n{:02x?}\n",
+ buf.as_slice(),
+ vec.as_slice(),
+ unpacked.as_slice(),
+ );
+ }
+ }
+
+ vec
+ }
+}
+
+/// Mosaic the U and V channels.
+pub(crate) fn mosaic_uv(buf_u: &[u8; 64], buf_v: &[u8; 64]) -> ([u8; 16], [u8; 16]) {
+ let mut out_u = [0u8; 16];
+ let mut out_v = [0u8; 16];
+
+ for y in 0..4 {
+ for x in 0..4 {
+ let base = y * 16 + x * 2;
+
+ let u0 = buf_u[base] as usize;
+ let u1 = buf_u[base + 1] as usize;
+ let u2 = buf_u[base + 8] as usize;
+ let u3 = buf_u[base + 9] as usize;
+
+ let v0 = buf_v[base] as usize;
+ let v1 = buf_v[base + 1] as usize;
+ let v2 = buf_v[base + 8] as usize;
+ let v3 = buf_v[base + 9] as usize;
+
+ let base = y * 4 + x;
+ out_u[base] = ((u0 + u1 + u2 + u3) / 4) as u8;
+ out_v[base] = ((v0 + v1 + v2 + v3) / 4) as u8;
+ }
+ }
+
+ (out_u, out_v)
+}
+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +
//! mPic - Simple Lossy Compression Image Format for Embedded Platforms
+//!
+//! ## Features
+//!
+//! - Simple.
+//! - Lossy compression.
+//! - Small memory footprint, only a few hundred bytes of stack memory required for decoding.
+//! - Designed for 16bpp color images and supports `embedded-graphics`; add `features = ["embedded"]` to Cargo.toml.
+//! - Support for `no_std`, No `alloc` is needed for decoding.
+//!
+//! ## More information
+//!
+//! - See detail: <https://github.com/neri/mpic>
+//!
+
+#![cfg_attr(not(test), no_std)]
+
+use core::{mem::size_of, slice};
+#[cfg(feature = "embedded")]
+use embedded_graphics::prelude::Size;
+
+#[cfg(feature = "alloc")]
+extern crate alloc;
+
+mod decode;
+pub use decode::*;
+
+mod encode;
+pub use encode::*;
+
+mod chunk;
+pub mod color;
+
+#[cfg(test)]
+mod test;
+
+pub const PREFERRED_FILE_EXT: &str = "mpic";
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
+pub enum EncodeError {
+ InvalidInput,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
+pub enum DecodeError {
+ InvalidInput,
+ InvalidData,
+}
+
+#[repr(C, packed)]
+pub struct FileHeader {
+ magic: [u8; 4],
+ width: u16,
+ height: u16,
+ version: u8,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
+pub struct ImageInfo {
+ width: u16,
+ height: u16,
+}
+
+impl ImageInfo {
+ #[inline]
+ pub fn width(&self) -> u32 {
+ self.width as u32
+ }
+
+ #[inline]
+ pub fn height(&self) -> u32 {
+ self.height as u32
+ }
+}
+
+#[cfg(feature = "embedded")]
+impl From<ImageInfo> for Size {
+ #[inline]
+ fn from(info: ImageInfo) -> Self {
+ Self::new(info.width as u32, info.height as u32)
+ }
+}
+
+impl FileHeader {
+ pub const MINIMAL_SIZE: usize = size_of::<Self>();
+
+ pub const MAGIC: [u8; 4] = *b"\x00mpi";
+
+ pub const VER_ZERO: u8 = 0;
+ pub const VER_CURRENT: u8 = 1;
+
+ #[inline]
+ pub const fn new(width: u32, height: u32) -> Option<Self> {
+ if width == 0 || width > 0xFFFF || height == 0 || height > 0xFFFF {
+ return None;
+ }
+ let version = if (width & 7) == 0 && (height & 7) == 0 {
+ Self::VER_ZERO
+ } else {
+ Self::VER_CURRENT
+ };
+ Some(Self {
+ magic: Self::MAGIC,
+ version,
+ width: (width as u16).to_le(),
+ height: (height as u16).to_le(),
+ })
+ }
+
+ #[inline]
+ pub fn is_valid(&self) -> bool {
+ let width = self.width.to_le();
+ let height = self.height.to_le();
+ self.magic == Self::MAGIC
+ && (Self::VER_ZERO..=Self::VER_CURRENT).contains(&self.version)
+ && width > 0
+ && height > 0
+ }
+
+ #[inline]
+ pub fn from_bytes<'a>(blob: &'a [u8]) -> Option<&'a Self> {
+ if blob.len() < Self::MINIMAL_SIZE {
+ return None;
+ }
+ let header = unsafe { &*(blob.as_ptr() as *const FileHeader) };
+ header.is_valid().then(|| header)
+ }
+
+ #[inline]
+ pub fn bytes<'a>(&'a self) -> &'a [u8] {
+ unsafe { slice::from_raw_parts(self as *const _ as *const u8, size_of::<Self>()) }
+ }
+
+ #[inline]
+ pub fn info(&self) -> ImageInfo {
+ ImageInfo {
+ width: self.width.to_le(),
+ height: self.height.to_le(),
+ }
+ }
+}
+
fn:
) to \
+ restrict the search to a given item kind.","Accepted kinds are: fn
, mod
, struct
, \
+ enum
, trait
, type
, macro
, \
+ and const
.","Search functions by type signature (e.g., vec -> usize
or \
+ -> vec
or String, enum:Cow -> bool
)","You can look for items with an exact name by putting double quotes around \
+ your request: \"string\"
","Look for functions that accept or return \
+ slices and \
+ arrays by writing \
+ square brackets (e.g., -> [u8]
or [] -> Option
)","Look for items inside another one by searching for a path: vec::Vec
",].map(x=>""+x+"
").join("");const div_infos=document.createElement("div");addClass(div_infos,"infos");div_infos.innerHTML="${value.replaceAll(" ", " ")}
`}else{error[index]=value}});output+=`