breadx_image/
lib.rs

1//               Copyright John Nunley, 2022.
2// Distributed under the Boost Software License, Version 1.0.
3//       (See accompanying file LICENSE or copy at
4//         https://www.boost.org/LICENSE_1_0.txt)
5
6//! A library for dealing with the X11 image format.
7//!
8//! The X11 image format is the format of images accepted by the X11
9//! server. It is relatively tricky to manipulate; this crate exists
10//! to provide generic types to make manipulating and sending images
11//! significantly easier.
12
13#![no_std]
14#![forbid(unsafe_code, future_incompatible, rust_2018_idioms)]
15
16extern crate alloc;
17#[cfg(feature = "std")]
18extern crate std;
19
20mod bits;
21use alloc::{vec, vec::Vec};
22use bits::Bitfield;
23
24mod byte_tables;
25use byte_tables::{low_bits, reverse_bytes};
26
27mod ext;
28pub use ext::DisplayExt;
29
30#[cfg(feature = "async")]
31pub use ext::AsyncDisplayExt;
32
33mod subimage;
34
35use breadx::protocol::xproto::{
36    Drawable, Gcontext, GetImageReply, ImageFormat, ImageOrder, PutImageRequest, Setup,
37};
38use core::{
39    convert::{TryFrom, TryInto},
40    fmt,
41    num::TryFromIntError,
42};
43
44#[cfg(feature = "std")]
45use std::error::Error;
46
47/*
48
49Personal notes:
50
51There are two main image formats: XY and Z
52
53 - Z is how you'd normally see an image: a series of scanlines,
54   padded to a given scanline pad, where each scanline is a series
55   of pixels made of a set of bits. Each pixel can be either 1, 4,
56   8, 16, or 32 bits. We ensure this on a type-level by using
57   the `BitsPerPixel` enum type.
58
59   If the image has the MSB byte order, it needs to be normalized.
60   For BPP of 8 and above, the bytes can be reversed. For BPP of
61   4, the nibbles need to be reversed. For BPP of 1, normalization
62   is the exact same as in an XY image.
63
64   Note that the depth of a Z pixmap image may not be equal to the
65   BPP, but must be less than or equal to it. We truncate it using
66   a byte table otherwise.
67
68 - XY is the other image format. It consists of a series of "planes",
69   which is made up of scanlines containing quantums. A quantum is either
70   8 bits, 16 bits or 32 bits. We ensure this on a type-level by using
71   the `Quantum` enum type. Each quantum contains several bits each
72   representing a single bit of the pixel. The planes are ordered from
73   the most significant bit to the least significant bit, and there are
74   a number of planes equal to the depth. To reconstruct a pixel, find
75   each bit from the planes and put them together.
76
77   Note that there are two "sub-types" of this image type. XY pixmaps
78   operate as above, while XY bitmaps only have a depth of 1. XY bitmaps
79   are useful because they can be colored arbitrarily.
80
81   If the image has an MSB byte order or the server requires an MSB bit order,
82   the image needs to be normalized. If the byte order is not equal to the
83   bit order, the bytes can be reversed as above. If the bit order is MSB,
84   the bits of each byte need to be reversed. This is done by using a table
85   that should be computed at compile time. Thanks, const eval!
86
87 - The `left_pad` (*Family Guy style flashback occurs*) value should be zero for
88   all Z images, and the value at which the scanline starts for XY images. This
89   could probably be used to help crop XY images in the future.
90
91 - A Z pixmap with a depth of 1 is fundamentally identical in operation to an
92   XY bitmap. Therefore, operations on 1-depth Z pixmaps should degenerate into
93   operations on XY bitmaps.
94
95 - Setting a pixel in any image can be done by reading the corresponding bytes,
96   preforming bit manipulations, and then substituting the new bytes back into
97   the image.
98
99 - To crop an image, create another image of a smaller size, and then get/set
100   pixels. There may be a more optimized way of doing this using ZPixmaps.
101
102 - The `Image` struct should be able to be easily transformed into a PutImageRequest
103   struct so that way it can be sent across the wire. It should also be able to be
104   easily deserialized from a GetImageReply.
105
106 - A "subimage" of an `Image` may be able to be sent in a PutImageRequest. In the
107   worst case, this would degenerate into a `crop()` followed by the regular send.
108   However, we may be able to crop each individual scanline in order to format them
109   as a PutImageRequest. Depends on whether the quanums match up with the byte
110   boundaries.
111
112   We can use the RawRequest API to manually set up each scanline as its own
113   I/O slice in these cases.
114
115 */
116
117/// The valid values for the bits per pixel of an image.
118#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
119#[repr(u8)]
120pub enum BitsPerPixel {
121    /// 1-bit per pixel.
122    One = 1,
123    /// 4-bits per pixel.
124    Four = 4,
125    /// 8-bits per pixel.
126    Eight = 8,
127    /// 16-bits per pixel.
128    Sixteen = 16,
129    /// 32-bits per pixel.
130    ThirtyTwo = 32,
131}
132
133impl BitsPerPixel {
134    pub fn roundup_from_depth(depth: u8) -> BitsPerPixel {
135        match depth {
136            i if i <= 1 => BitsPerPixel::One,
137            i if i <= 4 => BitsPerPixel::Four,
138            i if i <= 8 => BitsPerPixel::Eight,
139            i if i <= 16 => BitsPerPixel::Sixteen,
140            _ => BitsPerPixel::ThirtyTwo,
141        }
142    }
143}
144
145impl TryFrom<u8> for BitsPerPixel {
146    type Error = InvalidNumber;
147
148    fn try_from(value: u8) -> Result<Self, Self::Error> {
149        match value {
150            1 => Ok(BitsPerPixel::One),
151            4 => Ok(BitsPerPixel::Four),
152            8 => Ok(BitsPerPixel::Eight),
153            16 => Ok(BitsPerPixel::Sixteen),
154            32 => Ok(BitsPerPixel::ThirtyTwo),
155            value => Err(InvalidNumber::out_of_range(value, &[1, 4, 8, 16, 32])),
156        }
157    }
158}
159
160/// The valid values for the quantum of an image.
161#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
162#[repr(u8)]
163pub enum Quantum {
164    /// 8-bits per pixel.
165    Eight = 8,
166    /// 16-bits per pixel.
167    Sixteen = 16,
168    /// 32-bits per pixel.
169    ThirtyTwo = 32,
170}
171
172impl TryFrom<u8> for Quantum {
173    type Error = InvalidNumber;
174
175    fn try_from(value: u8) -> Result<Self, Self::Error> {
176        match value {
177            8 => Ok(Quantum::Eight),
178            16 => Ok(Quantum::Sixteen),
179            32 => Ok(Quantum::ThirtyTwo),
180            value => Err(InvalidNumber::out_of_range(value, &[8, 16, 32])),
181        }
182    }
183}
184
185/// The format that an image can have.
186#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
187pub enum Format {
188    /// The image is in XY format.
189    ///
190    /// The image is serialized as a series of planes of pixels,
191    /// where one can determine the value by adding up the values
192    /// taken from the planes.
193    Xy {
194        /// The direct format of the image.
195        ///
196        /// This determines whether it is in bitmap format or
197        /// pixmap format.
198        format: XyFormatType,
199        /// The quantum of the image.
200        ///
201        /// Each scanline can be broken down into a series of values
202        /// of this type. For instance, if the quantum was 32, you
203        /// could represent the scanline as a slice of `u32`s, although
204        /// the padding may actually differ.
205        quantum: Quantum,
206        /// The bit order to be used in the image.
207        ///
208        /// This determines whether the leftmost bit is either the least or most
209        /// significant bit.
210        bit_order: ImageOrder,
211        /// Leftmost padding to apply.
212        left_pad: u8,
213    },
214    /// The image in in Z format.
215    ///
216    /// The image is serialized such that each pixel exists in its entire bit
217    /// pattern.
218    Z {
219        /// The depth of the image.
220        depth: u8,
221        /// The bits per pixel for this image.
222        ///
223        /// This value is always greater than or equal to the depth. If it is
224        /// larger, the least significant bits hold the data.
225        bits_per_pixel: BitsPerPixel,
226    },
227}
228
229/// Whether or not an XY image format is in bitmap or pixmap format.
230#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
231pub enum XyFormatType {
232    /// The image is in bitmap format.
233    Bitmap,
234    /// The image is in pixmap format, with the given depth.
235    Pixmap {
236        /// The depth of the image.
237        depth: u8,
238    },
239}
240
241impl Format {
242    pub fn depth(&self) -> u8 {
243        match self {
244            Format::Xy {
245                format: XyFormatType::Pixmap { depth },
246                ..
247            } => *depth,
248            Format::Xy { .. } => 1,
249            Format::Z { depth, .. } => *depth,
250        }
251    }
252
253    pub fn format(&self) -> ImageFormat {
254        match self {
255            Format::Xy {
256                format: XyFormatType::Bitmap,
257                ..
258            } => ImageFormat::XY_BITMAP,
259            Format::Xy {
260                format: XyFormatType::Pixmap { .. },
261                ..
262            } => ImageFormat::XY_PIXMAP,
263            Format::Z { .. } => ImageFormat::Z_PIXMAP,
264        }
265    }
266}
267
268/// Computes the number of bytes necessary to store the image with the given parameters.
269pub fn storage_bytes(
270    width: u16,
271    height: u16,
272    depth: u8,
273    bits_per_pixel: Option<BitsPerPixel>,
274    format: ImageFormat,
275    scanline_pad: u8,
276) -> usize {
277    let bytes_per_line = match format {
278        ImageFormat::Z_PIXMAP => {
279            let bpp =
280                bits_per_pixel.unwrap_or_else(|| BitsPerPixel::roundup_from_depth(depth)) as usize;
281            pad_to(pad_to(bpp * width as usize, 8) / 8, scanline_pad.into())
282        }
283        ImageFormat::XY_PIXMAP | ImageFormat::XY_BITMAP => {
284            pad_to(width.into(), scanline_pad.into())
285        }
286        _ => panic!("Unsupported image format: {:?}", format),
287    };
288
289    let plane_len = bytes_per_line * height as usize;
290
291    if matches!(format, ImageFormat::Z_PIXMAP) {
292        plane_len
293    } else {
294        plane_len * depth as usize
295    }
296}
297
298#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
299pub struct Image<Storage: ?Sized> {
300    /// The width of the image.
301    width: u16,
302    /// The height of the image.
303    height: u16,
304    /// The format used in the image.
305    image_format: Format,
306    /// The byte ordering used in the image.
307    ///
308    /// This applies to the scanline units used in the Z pixmap format,
309    /// or to the quantums used in the XY format.
310    byte_order: ImageOrder,
311    /// The scanline pad for each scanline, in bits.
312    scanline_pad: u8,
313    /// The storage system used to store bytes.
314    storage: Storage,
315}
316
317impl<Storage: ?Sized> AsRef<Storage> for Image<Storage> {
318    fn as_ref(&self) -> &Storage {
319        &self.storage
320    }
321}
322
323impl<Storage: ?Sized> AsMut<Storage> for Image<Storage> {
324    fn as_mut(&mut self) -> &mut Storage {
325        &mut self.storage
326    }
327}
328
329impl<Storage: AsRef<[u8]>> Image<Storage> {
330    /// Create a new `Image` from a `Storage` and its individual parameters.
331    pub fn new(
332        storage: Storage,
333        width: u16,
334        height: u16,
335        format: Format,
336        byte_order: ImageOrder,
337        scanline_pad: u8,
338    ) -> Self {
339        Self {
340            width,
341            height,
342            scanline_pad,
343            image_format: format,
344            byte_order,
345            storage,
346        }
347    }
348
349    /// Create a new `Image`, but use a specific `Display` to set certain important values.
350    pub fn with_display(
351        storage: Storage,
352        width: u16,
353        height: u16,
354        image_format: ImageFormat,
355        depth: u8,
356        setup: &Setup,
357    ) -> breadx::Result<Self> {
358        let byte_order = setup.image_byte_order;
359        let (format, scanline_pad) = match image_format {
360            ImageFormat::XY_BITMAP | ImageFormat::XY_PIXMAP => (
361                Format::Xy {
362                    format: match image_format {
363                        ImageFormat::XY_BITMAP => XyFormatType::Bitmap,
364                        ImageFormat::XY_PIXMAP => XyFormatType::Pixmap { depth },
365                        _ => unreachable!(),
366                    },
367                    quantum: setup
368                        .bitmap_format_scanline_unit
369                        .try_into()
370                        .expect("invalid quantum"),
371                    bit_order: byte_order,
372                    left_pad: 0,
373                },
374                setup.bitmap_format_scanline_pad,
375            ),
376            ImageFormat::Z_PIXMAP => {
377                let (bpp, scanline_pad) = setup
378                    .pixmap_formats
379                    .iter()
380                    .find(|format| format.depth == depth)
381                    .map(|format| {
382                        (
383                            format.bits_per_pixel.try_into().expect("invalid bpp"),
384                            format.scanline_pad,
385                        )
386                    })
387                    .unwrap_or_else(|| {
388                        let bpp = BitsPerPixel::roundup_from_depth(depth);
389                        (bpp, setup.bitmap_format_scanline_pad)
390                    });
391
392                (
393                    Format::Z {
394                        depth,
395                        bits_per_pixel: bpp,
396                    },
397                    scanline_pad,
398                )
399            }
400            _ => panic!("Unsupported image format"),
401        };
402
403        Ok(Self::new(
404            storage,
405            width,
406            height,
407            format,
408            byte_order,
409            scanline_pad,
410        ))
411    }
412
413    /// Convert the image back into its `Storage`.
414    pub fn into_storage(self) -> Storage {
415        self.storage
416    }
417
418    /// Map the storage for this image.
419    ///
420    /// This function assumes that nothing else about the image is changed.
421    pub fn map_storage<R, F>(self, f: F) -> Image<R>
422    where
423        F: FnOnce(Storage) -> R,
424    {
425        let Self {
426            width,
427            height,
428            image_format,
429            byte_order,
430            scanline_pad,
431            storage,
432        } = self;
433
434        Image {
435            width,
436            height,
437            image_format,
438            byte_order,
439            scanline_pad,
440            storage: f(storage),
441        }
442    }
443}
444
445impl<Storage: ?Sized> Image<Storage> {
446    /// The number of bits in a scanline.
447    fn bits_per_scanline(&self) -> usize {
448        let unpadded_scanline = match self.image_format {
449            Format::Xy { ref left_pad, .. } => self.width as usize + *left_pad as usize,
450            Format::Z {
451                ref bits_per_pixel, ..
452            } => self.width as usize * *bits_per_pixel as usize,
453        };
454
455        // pad scanline to scanline_pad
456        let scanline_pad = self.scanline_pad as usize;
457        pad_to(unpadded_scanline, scanline_pad)
458    }
459
460    fn bytes_per_scanline(&self) -> usize {
461        self.bits_per_scanline() / 8
462    }
463
464    /// The size of the plane to skip when x-y indexing.
465    fn plane_size(&self) -> usize {
466        self.bits_per_scanline() * self.height as usize
467    }
468
469    /// Get the depth of this image.
470    pub fn depth(&self) -> u8 {
471        match self.image_format {
472            Format::Xy {
473                format: XyFormatType::Pixmap { depth },
474                ..
475            } => depth,
476            Format::Z { depth, .. } => depth,
477            Format::Xy {
478                format: XyFormatType::Bitmap,
479                ..
480            } => 1,
481        }
482    }
483
484    fn quantum(&self) -> usize {
485        match self.image_format {
486            Format::Xy { quantum, .. } => quantum as usize,
487            _ => 8,
488        }
489    }
490
491    fn left_pad(&self) -> usize {
492        match self.image_format {
493            Format::Xy { left_pad, .. } => left_pad as usize,
494            _ => 0,
495        }
496    }
497
498    /// Determine the index of where the quantum starts for an XY image.
499    fn quantum_xy_index(&self, x: usize, y: usize) -> usize {
500        let quantum = self.quantum();
501        let left_pad = self.left_pad();
502
503        (y * self.bytes_per_scanline()) + ((x + left_pad) / quantum) * (quantum / 8)
504    }
505
506    fn bits_per_pixel(&self) -> usize {
507        match self.image_format {
508            Format::Z { bits_per_pixel, .. } => bits_per_pixel as usize,
509            _ => unreachable!(),
510        }
511    }
512
513    fn z_index(&self, x: usize, y: usize) -> usize {
514        let bits_per_pixel = self.bits_per_pixel();
515
516        (y * self.bytes_per_scanline()) + ((x * bits_per_pixel) / 8)
517    }
518
519    /// Given a set of bits from an XY setup, normalize the bits.
520    fn normalize_bits_xy(&self, bits: &mut [u8]) {
521        let (bitmap_format, quantum) = match self.image_format {
522            Format::Xy {
523                ref bit_order,
524                ref quantum,
525                ..
526            } => (*bit_order, *quantum),
527            Format::Z { .. } => (
528                self.byte_order,
529                Quantum::try_from(self.scanline_pad).unwrap(),
530            ),
531        };
532
533        if matches!(
534            (self.byte_order, bitmap_format),
535            (ImageOrder::LSB_FIRST, ImageOrder::LSB_FIRST)
536        ) {
537            // no normalization needed
538            return;
539        }
540
541        if self.byte_order != bitmap_format {
542            match quantum {
543                Quantum::Sixteen => {
544                    // swap first two bytes
545                    debug_assert_eq!(bits.len(), 2);
546                    bits.swap(0, 1);
547                }
548                Quantum::ThirtyTwo => {
549                    // swap other bytes
550                    bits.swap(0, 3);
551                    bits.swap(1, 2);
552                }
553                _ => {}
554            }
555        }
556
557        if matches!(bitmap_format, ImageOrder::MSB_FIRST) {
558            let quantum = quantum as usize;
559            reverse_bytes(&mut bits[0..quantum / 8])
560        }
561    }
562
563    fn normalize_bits_z(&self, bits: &mut [u8]) {
564        if matches!(self.byte_order, ImageOrder::LSB_FIRST) {
565            return;
566        }
567
568        match self.bits_per_pixel() {
569            4 => {
570                // swap first two nibbles
571                bits[0] = ((bits[0] >> 4) & 0x0F) | ((bits[0] << 4) & 0xF0);
572            }
573            8 => {}
574            16 => {
575                // swap first two bytes
576                bits.swap(0, 1);
577            }
578            32 => {
579                // swap other bytes
580                bits.swap(0, 3);
581                bits.swap(1, 2);
582            }
583            bpp => tracing::error!("invalid bits per pixel: {}", bpp),
584        }
585    }
586
587    pub fn storage(&self) -> &Storage {
588        &self.storage
589    }
590
591    pub fn storage_mut(&mut self) -> &mut Storage {
592        &mut self.storage
593    }
594
595    pub fn width(&self) -> usize {
596        self.width.into()
597    }
598
599    pub fn height(&self) -> usize {
600        self.height.into()
601    }
602
603    pub fn format(&self) -> &Format {
604        &self.image_format
605    }
606}
607
608impl<Storage: AsRef<[u8]> + ?Sized> Image<Storage> {
609    pub fn borrow(&self) -> Image<&[u8]> {
610        Image {
611            width: self.width,
612            height: self.height,
613            image_format: self.image_format,
614            byte_order: self.byte_order,
615            scanline_pad: self.scanline_pad,
616            storage: self.storage.as_ref(),
617        }
618    }
619
620    /// Iterate over the regions of the image consisting of planes.
621    fn planes(&self) -> impl Iterator<Item = &[u8]> + '_ {
622        self.storage
623            .as_ref()
624            .chunks(self.plane_size())
625            .take(self.depth().into())
626    }
627
628    /// Assuming this is an X-Y format image, return the value of the pixel
629    /// at the given coordinates.
630    fn pixel_xy(&self, x: usize, y: usize) -> u32 {
631        let bit_index = (x + self.left_pad()) % self.quantum();
632        let quantum_bytes = self.quantum() / 8;
633        let addr = self.quantum_xy_index(x, y);
634        let mut result = 0;
635
636        // iterate over planes
637        for plane in self.planes() {
638            // copy the intended bytes into a buffer
639            let mut pixel = [0u8; 4];
640            let data_slice = &plane[addr..addr + quantum_bytes];
641            pixel[..quantum_bytes].copy_from_slice(data_slice);
642
643            // normalize the bits for MSB
644            self.normalize_bits_xy(&mut pixel[..quantum_bytes]);
645
646            // shift the bits into the result
647            let bit = u32::from(pixel[bit_index / 8]) >> (bit_index % 8);
648            result = result << 1 | (bit & 1);
649        }
650
651        result
652    }
653
654    /// Assumming this is a Z-format image, return the value of the pixel
655    /// at the given coordinates.
656    fn pixel_z(&self, x: usize, y: usize) -> u32 {
657        let mut pixel = [0u8; 4];
658        let addr = self.z_index(x, y);
659        let quantum_bytes = (self.bits_per_pixel() + 7) / 8;
660
661        // copy the intended bytes into a buffer
662        let data = &self.storage.as_ref()[addr..addr + quantum_bytes];
663        pixel[..quantum_bytes].copy_from_slice(data);
664        self.normalize_bits_z(&mut pixel[..quantum_bytes]);
665
666        // format bytes into a u32
667        let mut res = u32::from_le_bytes(pixel);
668
669        // flatten if needed
670        if self.bits_per_pixel() == 4 {
671            if x & 1 == 0 {
672                res &= 0x0F;
673            } else {
674                res >>= 4;
675            }
676        }
677
678        if self.bits_per_pixel() != self.depth() as usize {
679            // only get the needed bits
680            res = low_bits(res, self.depth() as usize);
681        }
682
683        res
684    }
685
686    /// Get the value of the pixel at the given coordinates.
687    pub fn pixel(&self, x: usize, y: usize) -> u32 {
688        // TODO: 1-depth Z pixmaps are
689        match self.image_format {
690            Format::Xy { .. } | Format::Z { depth: 1, .. } => self.pixel_xy(x, y),
691            Format::Z { .. } => self.pixel_z(x, y),
692        }
693    }
694
695    /// Create a [`PutImageRequest`] for this image.
696    pub fn put_image_request(
697        &self,
698        drawable: impl Into<Drawable>,
699        gc: impl Into<Gcontext>,
700        dst_x: i16,
701        dst_y: i16,
702    ) -> PutImageRequest<'_> {
703        let drawable = drawable.into();
704        let gc = gc.into();
705        let mut total_len = self.bytes_per_scanline() * self.height();
706        if matches!(self.format(), Format::Xy { .. }) {
707            total_len *= self.depth() as usize;
708        }
709
710        PutImageRequest {
711            format: self.image_format.format(),
712            drawable,
713            gc,
714            width: self.width,
715            height: self.height,
716            dst_x,
717            dst_y,
718            left_pad: match self.image_format {
719                Format::Xy { ref left_pad, .. } => *left_pad,
720                _ => 0,
721            },
722            depth: self.depth(),
723            data: self.storage.as_ref()[..total_len].into(),
724        }
725    }
726
727    /// Crop this image down to the given dimensions.
728    ///
729    /// ## Performance
730    ///
731    /// This function creates a new image and then manually copies all of the
732    /// pixels from this image to that one, using the `pixel` and `set_pixel`
733    /// functions. This usually leads to poor performance.
734    pub fn crop(&self, x: usize, y: usize, width: usize, height: usize) -> Image<Vec<u8>> {
735        // create a new empty image
736        let bpp = match self.image_format {
737            Format::Xy { .. } => None,
738            Format::Z {
739                ref bits_per_pixel, ..
740            } => Some(*bits_per_pixel),
741        };
742        let len = storage_bytes(
743            width as _,
744            height as _,
745            self.depth(),
746            bpp,
747            self.format().format(),
748            self.scanline_pad,
749        );
750        let storage = vec![0u8; len];
751        let mut image = Image::new(
752            storage,
753            width as _,
754            height as _,
755            self.image_format,
756            self.byte_order,
757            self.scanline_pad,
758        );
759
760        // TODO: do we need to consider left_pad?
761
762        // begin a pixelwise copy
763        for dest_y in 0..height {
764            for dest_x in 0..width {
765                let src_x = x + dest_x;
766                let src_y = y + dest_y;
767
768                let src_pixel = self.pixel(src_x, src_y);
769                image.set_pixel(dest_x, dest_y, src_pixel);
770            }
771        }
772
773        // we're done
774        image
775    }
776}
777
778impl<Storage: AsMut<[u8]> + ?Sized> Image<Storage> {
779    pub fn borrow_mut(&mut self) -> Image<&mut [u8]> {
780        Image {
781            storage: self.storage.as_mut(),
782            width: self.width,
783            height: self.height,
784            image_format: self.image_format,
785            byte_order: self.byte_order,
786            scanline_pad: self.scanline_pad,
787        }
788    }
789
790    /// Assuming this is an X-Y format image, set the value of a pixel at the
791    /// given coordinates.
792    fn set_pixel_xy(&mut self, x: usize, y: usize, pixel: u32) {
793        let bit_index = (x + self.left_pad()) % self.quantum();
794        let quantum_bytes = self.quantum() / 8;
795        let addr = self.quantum_xy_index(x, y);
796        let plane_size = self.plane_size();
797
798        for (i, planestart) in (0..self.depth() as usize)
799            .map(|i| i * plane_size)
800            .rev()
801            .enumerate()
802        {
803            // copy the current bytes into a buffer
804            let mut buffer = [0u8; 4];
805            let base = addr + planestart;
806            let data_slice = &mut self.storage.as_mut()[base..base + quantum_bytes];
807            buffer[..quantum_bytes].copy_from_slice(data_slice);
808
809            // normalize the bits for MSB
810            self.normalize_bits_xy(&mut buffer);
811
812            // create bitfields for the target and source,
813            // then preform a bitwise copy
814            let mut buffer_bits = Bitfield(&mut buffer);
815            let pixel_bits = Bitfield(pixel.to_ne_bytes());
816            buffer_bits.set_bit(bit_index, pixel_bits.bit(i));
817
818            // substitute buffer back into image
819            self.normalize_bits_xy(&mut buffer);
820            let data_slice = &mut self.storage.as_mut()[base..base + quantum_bytes];
821            data_slice.copy_from_slice(&buffer[..quantum_bytes]);
822        }
823    }
824
825    /// Assuming this is a Z format image, set the value of a pixel at the
826    /// given coordinates.
827    fn set_pixel_z(&mut self, x: usize, y: usize, pixel: u32) {
828        let addr = self.z_index(x, y);
829        let bpp = self.bits_per_pixel();
830        let quantum_bytes = (bpp + 7) / 8;
831
832        // copy into a buffer
833        let mut buffer = [0u8; 4];
834        let data_slice = &mut self.storage.as_mut()[addr..addr + quantum_bytes];
835        buffer[..quantum_bytes].copy_from_slice(data_slice);
836
837        // normalize the bits for MSB
838        self.normalize_bits_z(&mut buffer[..quantum_bytes]);
839
840        // preform a bitwise copy
841        let src = Bitfield(pixel.to_le_bytes());
842        let mut dest = Bitfield(&mut buffer);
843        let dest_addr = (x * bpp) % 8;
844        dest.copy_from(&src, dest_addr, 0, bpp);
845
846        // normalize and insert into image
847        self.normalize_bits_z(&mut buffer[..quantum_bytes]);
848        let data_slice = &mut self.storage.as_mut()[addr..addr + quantum_bytes];
849        data_slice.copy_from_slice(&buffer[..quantum_bytes]);
850    }
851
852    /// Set the value of a pixel at the given coordinates.
853    pub fn set_pixel(&mut self, x: usize, y: usize, pixel: u32) {
854        match self.image_format {
855            Format::Xy { .. } | Format::Z { depth: 1, .. } => self.set_pixel_xy(x, y, pixel),
856            Format::Z { .. } => self.set_pixel_z(x, y, pixel),
857        }
858    }
859}
860
861impl Image<Vec<u8>> {
862    /// Create a new image based on a [`GetImageReply`].
863    ///
864    /// [`GetImageReply`]: breadx::protocol::xproto::GetImageReply
865    pub fn from_get_image_reply(
866        reply: GetImageReply,
867        width: u16,
868        height: u16,
869        format: ImageFormat,
870        setup: &Setup,
871    ) -> breadx::Result<Self> {
872        let GetImageReply { depth, data, .. } = reply;
873
874        Self::with_display(data, width, height, format, depth, setup)
875    }
876}
877
878fn pad_to(val: usize, pad: usize) -> usize {
879    val + (pad - (val % pad)) % pad
880}
881
882/// An invalid number was provided.
883#[derive(Debug, Clone, Copy)]
884pub struct InvalidNumber(Innards);
885
886#[derive(Debug, Clone, Copy)]
887enum Innards {
888    InvalidConversion(TryFromIntError),
889    NotInRange { value: u8, valid: &'static [u8] },
890}
891
892impl InvalidNumber {
893    fn out_of_range(value: u8, valid: &'static [u8]) -> InvalidNumber {
894        InvalidNumber(Innards::NotInRange { value, valid })
895    }
896}
897
898impl fmt::Display for InvalidNumber {
899    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
900        match self.0 {
901            Innards::InvalidConversion(_) => write!(f, "invalid int conversion"),
902            Innards::NotInRange { value, valid } => {
903                write!(f, "value {} not in range {:?}", value, valid)
904            }
905        }
906    }
907}
908
909#[cfg(feature = "std")]
910impl Error for InvalidNumber {}
911
912impl From<TryFromIntError> for InvalidNumber {
913    fn from(err: TryFromIntError) -> Self {
914        InvalidNumber(Innards::InvalidConversion(err))
915    }
916}
917
918#[allow(clippy::needless_range_loop)]
919#[cfg(all(test, feature = "std"))]
920mod tests {
921    use alloc::vec;
922    use breadx::protocol::xproto::{ImageFormat, ImageOrder};
923    use std::panic;
924
925    use super::{Format, Image, XyFormatType};
926
927    const TEST_WIDTH: usize = 35;
928    const TEST_HEIGHT: usize = 35;
929
930    fn test_permutation(format: Format, depth: u8, scanline_pad: u8, byte_order: ImageOrder) {
931        // generate a random 35x35 image
932        let mut image = [[0u32; TEST_WIDTH]; TEST_HEIGHT];
933        for y in 0..TEST_HEIGHT {
934            for x in 0..TEST_WIDTH {
935                let limit = if depth == 32 {
936                    u32::MAX
937                } else {
938                    2_u32.pow(depth as u32)
939                };
940                image[y][x] = fastrand::u32(..limit);
941            }
942        }
943
944        // create an image to test with
945        let len = crate::storage_bytes(
946            TEST_WIDTH as _,
947            TEST_HEIGHT as _,
948            depth,
949            None,
950            format.format(),
951            scanline_pad,
952        );
953        let storage = vec![0u8; len];
954        let mut ximage = Image::new(
955            storage,
956            TEST_WIDTH as _,
957            TEST_HEIGHT as _,
958            format,
959            byte_order,
960            scanline_pad,
961        );
962
963        // populate with pixels
964        for y in 0..TEST_HEIGHT {
965            for x in 0..TEST_WIDTH {
966                ximage.set_pixel(x, y, image[y][x]);
967            }
968        }
969
970        // read pixels from image and compare to original
971        for y in 0..TEST_HEIGHT {
972            for x in 0..TEST_WIDTH {
973                assert_eq!(
974                    image[y][x],
975                    ximage.pixel(x, y),
976                    "Pixel at ({}, {}) not equal for format {:?}, byte order {:?} and depth {}",
977                    x,
978                    y,
979                    format,
980                    byte_order,
981                    depth
982                );
983            }
984        }
985    }
986
987    #[test]
988    fn test_get_and_set_pixel() {
989        for format in &[
990            ImageFormat::Z_PIXMAP,
991            ImageFormat::XY_PIXMAP,
992            ImageFormat::XY_BITMAP,
993        ] {
994            for depth in 1u8..=32 {
995                // x-y bitmaps only ever have a depth of 1
996                if *format == ImageFormat::XY_BITMAP && depth != 1 {
997                    continue;
998                }
999
1000                // this is broken for now, TODO
1001                if *format == ImageFormat::Z_PIXMAP && depth == 1 {
1002                    continue;
1003                }
1004
1005                let scanline_pad = 32;
1006
1007                for byte_order in &[ImageOrder::LSB_FIRST, ImageOrder::MSB_FIRST] {
1008                    for bit_order in &[ImageOrder::LSB_FIRST, ImageOrder::MSB_FIRST] {
1009                        // bit order doesn't matter for Z Pixmaps
1010                        if *format == ImageFormat::Z_PIXMAP && *bit_order != ImageOrder::LSB_FIRST {
1011                            continue;
1012                        }
1013
1014                        // make the format struct
1015                        let format = match *format {
1016                            ImageFormat::XY_BITMAP => Format::Xy {
1017                                format: XyFormatType::Bitmap,
1018                                quantum: crate::Quantum::ThirtyTwo,
1019                                bit_order: *bit_order,
1020                                left_pad: 0,
1021                            },
1022                            ImageFormat::XY_PIXMAP => Format::Xy {
1023                                format: XyFormatType::Pixmap { depth },
1024                                quantum: crate::Quantum::ThirtyTwo,
1025                                bit_order: *bit_order,
1026                                left_pad: 0,
1027                            },
1028                            ImageFormat::Z_PIXMAP => Format::Z {
1029                                depth: depth as _,
1030                                bits_per_pixel: crate::BitsPerPixel::roundup_from_depth(depth as _),
1031                            },
1032                            _ => unreachable!(),
1033                        };
1034
1035                        if let Err(e) = panic::catch_unwind(|| {
1036                            test_permutation(format, depth, scanline_pad, *byte_order);
1037                        }) {
1038                            std::eprintln!(
1039                                "panic occurred with format {:?} and byte order {:?}",
1040                                format,
1041                                byte_order
1042                            );
1043                            panic::resume_unwind(e);
1044                        }
1045                    }
1046                }
1047            }
1048        }
1049    }
1050}
1051
1052pub mod prelude {
1053    #[cfg(feature = "async")]
1054    pub use crate::AsyncDisplayExt;
1055    pub use crate::DisplayExt;
1056}