Skip to main content

wml2/
draw.rs

1//! Callback-based image I/O primitives and the default RGBA image buffer.
2//!
3//! Decoders push pixels into a [`DrawCallback`] implementation. Encoders pull
4//! pixels from a [`PickCallback`] implementation. [`ImageBuffer`] provides the
5//! default in-memory implementation for both directions and stores animation
6//! frames as RGBA sub-rectangles.
7type Error = Box<dyn std::error::Error>;
8use crate::color::RGBA;
9use crate::error::ImgError;
10use crate::error::ImgErrorKind;
11use crate::metadata::DataMap;
12use crate::util::ImageFormat;
13use crate::util::format_check;
14use crate::warning::ImgWarnings;
15use bin_rs::reader::*;
16use std::collections::HashMap;
17#[cfg(not(target_family = "wasm"))]
18use std::io::BufRead;
19#[cfg(not(target_family = "wasm"))]
20use std::io::BufReader;
21use std::io::Seek;
22use std::io::SeekFrom;
23use std::io::Write;
24#[cfg(not(target_family = "wasm"))]
25use std::path::Path;
26
27/* Dynamic Select Callback System */
28
29/// Legacy multi-image control values kept for compatibility.
30#[derive(Debug)]
31pub enum DrawNextOptions {
32    Continue,
33    NextImage,
34    ClearNext,
35    WaitTime(usize),
36    None,
37}
38
39pub type Response = Result<Option<CallbackResponse>, Error>;
40
41/// Receives decoded image data from format decoders.
42pub trait DrawCallback: Sync + Send {
43    /// Initializes the target for a new image or animation canvas.
44    fn init(
45        &mut self,
46        width: usize,
47        height: usize,
48        option: Option<InitOptions>,
49    ) -> Result<Option<CallbackResponse>, Error>;
50    /// Writes RGBA pixels into a rectangle.
51    fn draw(
52        &mut self,
53        start_x: usize,
54        start_y: usize,
55        width: usize,
56        height: usize,
57        data: &[u8],
58        option: Option<DrawOptions>,
59    ) -> Result<Option<CallbackResponse>, Error>;
60    fn terminate(
61        &mut self,
62        _term: Option<TerminateOptions>,
63    ) -> Result<Option<CallbackResponse>, Error>;
64    /// Signals a new animation frame or image boundary.
65    fn next(&mut self, _next: Option<NextOptions>) -> Result<Option<CallbackResponse>, Error>;
66    /// Emits verbose decoder output.
67    fn verbose(
68        &mut self,
69        _verbose: &str,
70        _: Option<VerboseOptions>,
71    ) -> Result<Option<CallbackResponse>, Error>;
72    /// Stores decoded metadata.
73    fn set_metadata(
74        &mut self,
75        key: &str,
76        value: DataMap,
77    ) -> Result<Option<CallbackResponse>, Error>;
78}
79
80/// Supplies image data to encoders.
81pub trait PickCallback: Sync + Send {
82    /// Returns the base image profile used for encoding.
83    fn encode_start(
84        &mut self,
85        option: Option<EncoderOptions>,
86    ) -> Result<Option<ImageProfiles>, Error>;
87    /// Reads an RGBA rectangle from the image source.
88    fn encode_pick(
89        &mut self,
90        start_x: usize,
91        start_y: usize,
92        width: usize,
93        height: usize,
94        option: Option<PickOptions>,
95    ) -> Result<Option<Vec<u8>>, Error>;
96    /// Finalizes encoding.
97    fn encode_end(&mut self, _: Option<EndOptions>) -> Result<(), Error>;
98    /// Returns source metadata, if available.
99    fn metadata(&mut self) -> Result<Option<HashMap<String, DataMap>>, Error>;
100}
101
102/// Encoder-specific startup options.
103#[derive(Debug)]
104pub struct EncoderOptions {}
105
106/// Encoder read options for partial pixel fetches.
107#[derive(Debug)]
108pub struct PickOptions {}
109
110/// Encoder shutdown options.
111#[derive(Debug)]
112pub struct EndOptions {}
113
114#[allow(unused)]
115/// Canvas initialization options passed to [`DrawCallback::init`].
116#[derive(Debug)]
117pub struct InitOptions {
118    /// Animation loop count when known.
119    pub loop_count: u32,
120    /// Optional RGBA background color for the canvas.
121    pub background: Option<RGBA>,
122    /// Whether the decoded source is animated.
123    pub animation: bool,
124}
125
126impl InitOptions {
127    /// Creates default initialization options.
128    pub fn new() -> Option<Self> {
129        Some(Self {
130            loop_count: 1,
131            background: None,
132            animation: false,
133        })
134    }
135}
136
137/// Decoder-specific draw options.
138#[derive(Debug)]
139pub struct DrawOptions {}
140
141/// Decoder termination options.
142#[derive(Debug)]
143pub struct TerminateOptions {}
144
145/// Verbose logging options.
146#[derive(Debug)]
147pub struct VerboseOptions {}
148
149/// Frame transition commands used by animated decoders.
150#[derive(Debug)]
151pub enum NextOption {
152    /// Continue the current animation.
153    Continue,
154    /// Start a new frame or image.
155    Next,
156    /// Dispose the previous frame.
157    Dispose,
158    /// Clear the current frame and abort.
159    ClearAbort,
160    /// End the current animation stream.
161    Terminate,
162}
163
164/// Disposal mode for an animation frame.
165#[derive(Debug)]
166pub enum NextDispose {
167    /// Leave the previous frame as-is.
168    None,
169    /// Compatibility value retained from older callers.
170    Override,
171    /// Clear the frame area to the background color.
172    Background,
173    /// Restore the previous composited canvas.
174    Previous,
175}
176
177/// Blend mode for an animation frame.
178///
179/// `Source` means "alpha-blend onto the existing canvas". For APNG this maps to
180/// `OVER`, and for animated WebP it matches alpha blending. `Override` replaces
181/// the covered destination pixels.
182#[derive(Debug)]
183pub enum NextBlend {
184    Source,
185    Override,
186}
187
188#[allow(unused)]
189/// A rectangle on the destination canvas.
190#[derive(Debug)]
191pub struct ImageRect {
192    pub start_x: i32,
193    pub start_y: i32,
194    pub width: usize,
195    pub height: usize,
196}
197
198#[allow(unused)]
199/// Per-frame animation control values.
200#[derive(Debug)]
201pub struct NextOptions {
202    /// Transition command for this frame.
203    pub flag: NextOption,
204    /// Delay before advancing to the next frame, in milliseconds.
205    pub await_time: u64,
206    /// Frame rectangle on the animation canvas.
207    pub image_rect: Option<ImageRect>,
208    /// Frame disposal method.
209    pub dispose_option: Option<NextDispose>,
210    /// Frame blending method.
211    pub blend: Option<NextBlend>,
212}
213
214impl NextOptions {
215    /// Creates default frame options.
216    pub fn new() -> Self {
217        NextOptions {
218            flag: NextOption::Continue,
219            await_time: 0,
220            image_rect: None,
221            dispose_option: None,
222            blend: None,
223        }
224    }
225
226    /// Creates a frame option with only a delay.
227    pub fn wait(ms_time: u64) -> Self {
228        NextOptions {
229            flag: NextOption::Continue,
230            await_time: ms_time,
231            image_rect: None,
232            dispose_option: None,
233            blend: None,
234        }
235    }
236}
237
238/// Decoder response command.
239#[derive(std::cmp::PartialEq, Debug)]
240pub enum ResponseCommand {
241    Abort,
242    Continue,
243}
244
245/// Response returned by callbacks.
246#[derive(Debug)]
247pub struct CallbackResponse {
248    pub response: ResponseCommand,
249}
250
251impl CallbackResponse {
252    /// Builds an abort response.
253    pub fn abort() -> Self {
254        Self {
255            response: ResponseCommand::Abort,
256        }
257    }
258
259    /// Builds a continue response.
260    pub fn cont() -> Self {
261        Self {
262            response: ResponseCommand::Continue,
263        }
264    }
265}
266
267/// Static image properties returned from [`PickCallback::encode_start`].
268#[derive(Debug)]
269pub struct ImageProfiles {
270    /// Canvas width in pixels.
271    pub width: usize,
272    /// Canvas height in pixels.
273    pub height: usize,
274    /// Optional background color for formats that support it.
275    pub background: Option<RGBA>,
276    /// Source metadata.
277    ///
278    /// Built-in encoders may also consume reserved keys inserted by built-in
279    /// `PickCallback` implementations for animation transport.
280    pub metadata: Option<HashMap<String, DataMap>>,
281}
282
283pub(crate) const ENCODE_ANIMATION_FRAMES_KEY: &str = "wml2.animation.frames";
284pub(crate) const ENCODE_ANIMATION_LOOP_COUNT_KEY: &str = "wml2.animation.loop_count";
285
286pub(crate) fn encode_animation_frame_key(index: usize, field: &str) -> String {
287    format!("wml2.animation.frame.{index}.{field}")
288}
289
290fn encode_animation_dispose(dispose: &Option<NextDispose>) -> u64 {
291    match dispose {
292        Some(NextDispose::Background) => 1,
293        Some(NextDispose::Previous) => 2,
294        _ => 0,
295    }
296}
297
298fn encode_animation_blend(blend: &Option<NextBlend>) -> u64 {
299    match blend {
300        Some(NextBlend::Source) => 1,
301        _ => 0,
302    }
303}
304
305fn append_animation_metadata(
306    metadata: &mut HashMap<String, DataMap>,
307    animation: &[AnimationLayer],
308    loop_count: u32,
309) {
310    metadata.insert(
311        ENCODE_ANIMATION_FRAMES_KEY.to_string(),
312        DataMap::UInt(animation.len() as u64),
313    );
314    metadata.insert(
315        ENCODE_ANIMATION_LOOP_COUNT_KEY.to_string(),
316        DataMap::UInt(loop_count as u64),
317    );
318
319    for (index, layer) in animation.iter().enumerate() {
320        metadata.insert(
321            encode_animation_frame_key(index, "width"),
322            DataMap::UInt(layer.width as u64),
323        );
324        metadata.insert(
325            encode_animation_frame_key(index, "height"),
326            DataMap::UInt(layer.height as u64),
327        );
328        metadata.insert(
329            encode_animation_frame_key(index, "start_x"),
330            DataMap::SInt(layer.start_x as i64),
331        );
332        metadata.insert(
333            encode_animation_frame_key(index, "start_y"),
334            DataMap::SInt(layer.start_y as i64),
335        );
336        metadata.insert(
337            encode_animation_frame_key(index, "delay_ms"),
338            DataMap::UInt(layer.control.await_time),
339        );
340        metadata.insert(
341            encode_animation_frame_key(index, "dispose"),
342            DataMap::UInt(encode_animation_dispose(&layer.control.dispose_option)),
343        );
344        metadata.insert(
345            encode_animation_frame_key(index, "blend"),
346            DataMap::UInt(encode_animation_blend(&layer.control.blend)),
347        );
348        metadata.insert(
349            encode_animation_frame_key(index, "buffer"),
350            DataMap::Raw(layer.buffer.clone()),
351        );
352    }
353}
354
355/// One decoded animation frame stored as an RGBA sub-rectangle.
356pub struct AnimationLayer {
357    /// Frame width in pixels.
358    pub width: usize,
359    /// Frame height in pixels.
360    pub height: usize,
361    /// Frame x offset on the canvas.
362    pub start_x: i32,
363    /// Frame y offset on the canvas.
364    pub start_y: i32,
365    /// RGBA frame pixels in row-major order.
366    pub buffer: Vec<u8>,
367    /// Frame timing and composition settings.
368    pub control: NextOptions,
369}
370
371#[allow(unused)]
372/// Default in-memory RGBA image store used by decoders and encoders.
373pub struct ImageBuffer {
374    /// Canvas width in pixels.
375    pub width: usize,
376    /// Canvas height in pixels.
377    pub height: usize,
378    /// Optional background color.
379    pub background_color: Option<RGBA>,
380    /// Base canvas RGBA pixels.
381    pub buffer: Option<Vec<u8>>,
382    /// Animation frames, if present.
383    pub animation: Option<Vec<AnimationLayer>>,
384    /// Current animation frame index while decoding.
385    pub current: Option<usize>,
386    /// Animation loop count, if known.
387    pub loop_count: Option<u32>,
388    /// Delay of the first frame, if known.
389    pub first_wait_time: Option<u64>,
390    fnverbose: fn(&str) -> Result<Option<CallbackResponse>, Error>,
391    /// Arbitrary metadata collected during decode.
392    pub metadata: Option<HashMap<String, DataMap>>,
393}
394
395fn default_verbose(_: &str) -> Result<Option<CallbackResponse>, Error> {
396    Ok(None)
397}
398
399fn checked_rgba_len(width: usize, height: usize, context: &str) -> Result<usize, Error> {
400    width
401        .checked_mul(height)
402        .and_then(|pixels| pixels.checked_mul(4))
403        .ok_or_else(|| {
404            Box::new(ImgError::new_const(
405                ImgErrorKind::InvalidParameter,
406                format!("{context} buffer size overflow"),
407            )) as Error
408        })
409}
410
411impl ImageBuffer {
412    /// Creates an empty image buffer.
413    pub fn new() -> Self {
414        Self {
415            width: 0,
416            height: 0,
417            background_color: None,
418            buffer: None,
419            animation: None,
420            current: None,
421            loop_count: None,
422            first_wait_time: None,
423            fnverbose: default_verbose,
424            metadata: None,
425        }
426    }
427
428    /// Creates an image buffer from an RGBA pixel buffer.
429    pub fn from_buffer(width: usize, height: usize, buf: Vec<u8>) -> Self {
430        Self {
431            width,
432            height,
433            background_color: None,
434            buffer: Some(buf),
435            animation: None,
436            current: None,
437            loop_count: None,
438            first_wait_time: None,
439            fnverbose: default_verbose,
440            metadata: None,
441        }
442    }
443
444    /// Enables or disables animation storage.
445    pub fn set_animation(&mut self, flag: bool) {
446        if flag {
447            self.animation = Some(Vec::new())
448        } else {
449            self.animation = None
450        }
451    }
452
453    /// Installs a verbose logging callback used by decoders.
454    pub fn set_verbose(&mut self, verbose: fn(&str) -> Result<Option<CallbackResponse>, Error>) {
455        self.fnverbose = verbose;
456    }
457}
458
459impl DrawCallback for ImageBuffer {
460    /// Initializes the in-memory canvas.
461    fn init(
462        &mut self,
463        width: usize,
464        height: usize,
465        option: Option<InitOptions>,
466    ) -> Result<Option<CallbackResponse>, Error> {
467        let buffersize = checked_rgba_len(width, height, "image")?;
468        self.width = width;
469        self.height = height;
470        if let Some(option) = option {
471            self.background_color = option.background;
472            if option.animation {
473                self.set_animation(true);
474            }
475            self.loop_count = Some(option.loop_count);
476        }
477        if let Some(background) = &self.background_color {
478            self.buffer = Some(
479                (0..buffersize)
480                    .map(|i| match i % 4 {
481                        0 => background.red,
482                        1 => background.green,
483                        2 => background.blue,
484                        _ => background.alpha,
485                    })
486                    .collect(),
487            );
488        } else {
489            self.buffer = Some((0..buffersize).map(|_| 0).collect());
490        }
491
492        Ok(None)
493    }
494
495    /// Draws part of the current image or animation frame.
496    fn draw(
497        &mut self,
498        start_x: usize,
499        start_y: usize,
500        width: usize,
501        height: usize,
502        data: &[u8],
503        _: Option<DrawOptions>,
504    ) -> Result<Option<CallbackResponse>, Error> {
505        if self.buffer.is_none() {
506            return Err(Box::new(ImgError::new_const(
507                ImgErrorKind::NotInitializedImageBuffer,
508                "in draw".to_string(),
509            )));
510        }
511        let buffer;
512        let (w, h, raws);
513        if self.current.is_none() {
514            if start_x >= self.width || start_y >= self.height {
515                return Ok(None);
516            }
517            let requested_end_x = start_x.checked_add(width).ok_or_else(|| {
518                Box::new(ImgError::new_const(
519                    ImgErrorKind::OutboundIndex,
520                    "draw rectangle x overflow".to_string(),
521                )) as Error
522            })?;
523            let requested_end_y = start_y.checked_add(height).ok_or_else(|| {
524                Box::new(ImgError::new_const(
525                    ImgErrorKind::OutboundIndex,
526                    "draw rectangle y overflow".to_string(),
527                )) as Error
528            })?;
529            w = self.width.min(requested_end_x) - start_x;
530            h = self.height.min(requested_end_y) - start_y;
531            raws = self.width;
532            buffer = self.buffer.as_deref_mut().ok_or_else(|| {
533                Box::new(ImgError::new_const(
534                    ImgErrorKind::NotInitializedImageBuffer,
535                    "buffer is not initialized".to_string(),
536                )) as Error
537            })?;
538        } else if let Some(animation) = &mut self.animation {
539            let current = self.current.ok_or_else(|| {
540                Box::new(ImgError::new_const(
541                    ImgErrorKind::IllegalData,
542                    "animation frame is not selected".to_string(),
543                )) as Error
544            })?;
545            if start_x >= animation[current].width || start_y >= animation[current].height {
546                return Ok(None);
547            }
548            let requested_end_x = start_x.checked_add(width).ok_or_else(|| {
549                Box::new(ImgError::new_const(
550                    ImgErrorKind::OutboundIndex,
551                    "draw rectangle x overflow".to_string(),
552                )) as Error
553            })?;
554            let requested_end_y = start_y.checked_add(height).ok_or_else(|| {
555                Box::new(ImgError::new_const(
556                    ImgErrorKind::OutboundIndex,
557                    "draw rectangle y overflow".to_string(),
558                )) as Error
559            })?;
560            w = animation[current].width.min(requested_end_x) - start_x;
561            h = animation[current].height.min(requested_end_y) - start_y;
562            raws = animation[current].width;
563            buffer = &mut animation[current].buffer;
564        } else {
565            return Err(Box::new(ImgError::new_const(
566                ImgErrorKind::NotInitializedImageBuffer,
567                "in animation".to_string(),
568            )));
569        }
570
571        for y in 0..h {
572            let scanline_src = y * width * 4;
573            let scanline_dest = (start_y + y) * raws * 4;
574            for x in 0..w {
575                let offset_src = scanline_src + x * 4;
576                let offset_dest = scanline_dest + (x + start_x) * 4;
577                if offset_src + 3 >= data.len() {
578                    return Err(Box::new(ImgError::new_const(
579                        ImgErrorKind::OutboundIndex,
580                        "decoder buffer in draw".to_string(),
581                    )));
582                }
583                if offset_dest + 3 >= buffer.len() {
584                    return Err(Box::new(ImgError::new_const(
585                        ImgErrorKind::OutboundIndex,
586                        "image buffer in draw".to_string(),
587                    )));
588                }
589                buffer[offset_dest] = data[offset_src];
590                buffer[offset_dest + 1] = data[offset_src + 1];
591                buffer[offset_dest + 2] = data[offset_src + 2];
592                buffer[offset_dest + 3] = data[offset_src + 3];
593            }
594        }
595        Ok(None)
596    }
597
598    /// Finalizes decoding.
599    fn terminate(
600        &mut self,
601        _: Option<TerminateOptions>,
602    ) -> Result<Option<CallbackResponse>, Error> {
603        Ok(None)
604    }
605
606    /// Starts a new animation frame in the buffer.
607    fn next(&mut self, opt: Option<NextOptions>) -> Result<Option<CallbackResponse>, Error> {
608        if self.animation.is_some() {
609            if let Some(opt) = opt {
610                if self.current.is_none() {
611                    self.current = Some(0);
612                    self.first_wait_time = Some(opt.await_time);
613                } else {
614                    self.current = Some(
615                        self.current.ok_or_else(|| {
616                            Box::new(ImgError::new_const(
617                                ImgErrorKind::IllegalData,
618                                "animation frame is not selected".to_string(),
619                            )) as Error
620                        })? + 1,
621                    );
622                }
623                let (width, height, start_x, start_y);
624                if let Some(ref rect) = opt.image_rect {
625                    width = rect.width;
626                    height = rect.height;
627                    start_x = rect.start_x;
628                    start_y = rect.start_y;
629                } else {
630                    width = self.width;
631                    height = self.height;
632                    start_x = 0;
633                    start_y = 0;
634                }
635                let buffersize = checked_rgba_len(width, height, "animation frame")?;
636                let buffer: Vec<u8> = (0..buffersize).map(|_| 0).collect();
637                let layer = AnimationLayer {
638                    width,
639                    height,
640                    start_x,
641                    start_y,
642                    buffer,
643                    control: opt,
644                };
645
646                if let Some(animation) = self.animation.as_mut() {
647                    animation.push(layer);
648                } else {
649                    return Err(Box::new(ImgError::new_const(
650                        ImgErrorKind::NotInitializedImageBuffer,
651                        "animation buffer is not initialized".to_string(),
652                    )));
653                }
654
655                return Ok(Some(CallbackResponse::cont()));
656            }
657        }
658        Ok(Some(CallbackResponse::abort()))
659    }
660
661    /// Passes through verbose decoder output.
662    fn verbose(
663        &mut self,
664        str: &str,
665        _: Option<VerboseOptions>,
666    ) -> Result<Option<CallbackResponse>, Error> {
667        (self.fnverbose)(str)
668    }
669
670    /// Stores decoded metadata on the buffer.
671    fn set_metadata(
672        &mut self,
673        key: &str,
674        value: DataMap,
675    ) -> Result<Option<CallbackResponse>, Error> {
676        let hashmap = if let Some(ref mut hashmap) = self.metadata {
677            hashmap
678        } else {
679            self.metadata = Some(HashMap::new());
680            self.metadata.as_mut().ok_or_else(|| {
681                Box::new(ImgError::new_const(
682                    ImgErrorKind::IllegalData,
683                    "metadata store is not initialized".to_string(),
684                )) as Error
685            })?
686        };
687        hashmap.insert(key.to_string(), value);
688
689        Ok(None)
690    }
691}
692
693impl PickCallback for ImageBuffer {
694    /// Exposes the image profile to encoders.
695    fn encode_start(&mut self, _: Option<EncoderOptions>) -> Result<Option<ImageProfiles>, Error> {
696        let mut metadata = self.metadata.clone();
697        if let Some(animation) = &self.animation {
698            if !animation.is_empty() {
699                let hashmap = if let Some(ref mut metadata) = metadata {
700                    metadata
701                } else {
702                    metadata = Some(HashMap::new());
703                    metadata.as_mut().ok_or_else(|| {
704                        Box::new(ImgError::new_const(
705                            ImgErrorKind::IllegalData,
706                            "metadata store is not initialized".to_string(),
707                        )) as Error
708                    })?
709                };
710                append_animation_metadata(hashmap, animation, self.loop_count.unwrap_or(0));
711            }
712        }
713        let init = ImageProfiles {
714            width: self.width,
715            height: self.height,
716            background: self.background_color.clone(),
717            metadata,
718        };
719        Ok(Some(init))
720    }
721
722    /// Reads an RGBA rectangle from the base canvas.
723    fn encode_pick(
724        &mut self,
725        start_x: usize,
726        start_y: usize,
727        width: usize,
728        height: usize,
729        _: Option<PickOptions>,
730    ) -> Result<Option<Vec<u8>>, Error> {
731        if self.buffer.is_none() {
732            return Err(Box::new(ImgError::new_const(
733                ImgErrorKind::NotInitializedImageBuffer,
734                "in pick".to_string(),
735            )));
736        }
737        let buffersize = checked_rgba_len(width, height, "pick")?;
738        let mut data = Vec::with_capacity(buffersize);
739        let buffer = self.buffer.as_ref().ok_or_else(|| {
740            Box::new(ImgError::new_const(
741                ImgErrorKind::NotInitializedImageBuffer,
742                "in pick".to_string(),
743            )) as Error
744        })?;
745
746        if start_x >= self.width || start_y >= self.height {
747            return Ok(None);
748        }
749        let requested_end_x = start_x.checked_add(width).ok_or_else(|| {
750            Box::new(ImgError::new_const(
751                ImgErrorKind::OutboundIndex,
752                "pick rectangle x overflow".to_string(),
753            )) as Error
754        })?;
755        let requested_end_y = start_y.checked_add(height).ok_or_else(|| {
756            Box::new(ImgError::new_const(
757                ImgErrorKind::OutboundIndex,
758                "pick rectangle y overflow".to_string(),
759            )) as Error
760        })?;
761        let w = self.width.min(requested_end_x) - start_x;
762        let h = self.height.min(requested_end_y) - start_y;
763
764        for y in 0..h {
765            let scanline_src = (start_y + y) * self.width * 4;
766            for x in 0..w {
767                let offset_src = scanline_src + (start_x + x) * 4;
768                if offset_src + 3 >= buffer.len() {
769                    return Err(Box::new(ImgError::new_const(
770                        ImgErrorKind::OutboundIndex,
771                        "Image buffer in pick".to_string(),
772                    )));
773                }
774                data.push(buffer[offset_src]);
775                data.push(buffer[offset_src + 1]);
776                data.push(buffer[offset_src + 2]);
777                data.push(buffer[offset_src + 3]);
778            }
779            for _ in w..width {
780                // 0 fill
781                data.push(0x00);
782                data.push(0x00);
783                data.push(0x00);
784                data.push(0x00);
785            }
786        }
787        for _ in h..height {
788            // 0 fill
789            for _ in 0..width {
790                data.push(0x00);
791                data.push(0x00);
792                data.push(0x00);
793                data.push(0x00);
794            }
795        }
796
797        Ok(Some(data))
798    }
799
800    /// Finalizes encoding.
801    fn encode_end(&mut self, _: Option<EndOptions>) -> Result<(), Error> {
802        Ok(())
803    }
804
805    /// Returns stored metadata.
806    fn metadata(&mut self) -> Result<Option<HashMap<String, DataMap>>, Error> {
807        if let Some(hashmap) = &self.metadata {
808            Ok(Some(hashmap.clone()))
809        } else {
810            Ok(None)
811        }
812    }
813}
814
815/// Decoder configuration.
816pub struct DecodeOptions<'a> {
817    /// Enables format-specific verbose output when non-zero.
818    pub debug_flag: usize,
819    /// Destination callback implementation.
820    pub drawer: &'a mut dyn DrawCallback,
821}
822
823/// Encoder configuration.
824pub struct EncodeOptions<'a> {
825    /// Enables encoder-specific verbose output when non-zero.
826    pub debug_flag: usize,
827    /// Source callback implementation.
828    pub drawer: &'a mut dyn PickCallback,
829    /// Encoder-specific options such as JPEG `quality`, TIFF `compression`,
830    /// WebP `quality` and `optimize`, or `exif`.
831    ///
832    /// `exif` accepts raw serialized EXIF bytes, TIFF-style EXIF headers, or
833    /// `Ascii("copy")` to reuse decoded source EXIF during [`convert`].
834    pub options: Option<HashMap<String, DataMap>>,
835}
836
837/// Decodes an image from memory into an [`ImageBuffer`].
838///
839/// # Examples
840/// ```rust
841/// use wml2::draw::{ImageBuffer, image_from, image_to};
842/// use wml2::util::ImageFormat;
843///
844/// let mut source = ImageBuffer::from_buffer(1, 1, vec![255, 0, 0, 255]);
845/// let png = image_to(&mut source, ImageFormat::Png, None).unwrap();
846///
847/// let image = image_from(&png).unwrap();
848/// assert_eq!(image.width, 1);
849/// assert_eq!(image.height, 1);
850/// ```
851pub fn image_from(buffer: &[u8]) -> Result<ImageBuffer, Error> {
852    image_load(buffer)
853}
854
855/// Decodes an image from a file into an [`ImageBuffer`].
856///
857/// # Examples
858/// ```no_run
859/// use wml2::draw::image_from_file;
860///
861/// let image = image_from_file("input.webp".to_string())?;
862/// assert!(image.width > 0);
863/// assert!(image.height > 0);
864/// # Ok::<(), Box<dyn std::error::Error>>(())
865/// ```
866#[cfg(not(target_family = "wasm"))]
867pub fn image_from_file(filename: String) -> Result<ImageBuffer, Error> {
868    let f = std::fs::File::open(filename)?;
869    let reader = BufReader::new(f);
870    let mut image = ImageBuffer::new();
871    let mut option = DecodeOptions {
872        debug_flag: 0x00,
873        drawer: &mut image,
874    };
875    let _ = image_reader(reader, &mut option)?;
876    Ok(image)
877}
878
879/// Encodes an image source and writes it to a file.
880///
881/// # Examples
882/// ```no_run
883/// use wml2::draw::{ImageBuffer, image_to_file};
884/// use wml2::util::ImageFormat;
885///
886/// let mut image = ImageBuffer::from_buffer(1, 1, vec![255, 0, 0, 255]);
887/// image_to_file("output.png".to_string(), &mut image, ImageFormat::Png)?;
888/// # Ok::<(), Box<dyn std::error::Error>>(())
889/// ```
890#[cfg(not(target_family = "wasm"))]
891pub fn image_to_file(
892    filename: String,
893    image: &mut dyn PickCallback,
894    format: ImageFormat,
895) -> Result<(), Error> {
896    let f = std::fs::File::create(filename)?;
897    let mut option = EncodeOptions {
898        debug_flag: 0x00,
899        drawer: image,
900        options: None,
901    };
902    image_writer(f, &mut option, format)?;
903    Ok(())
904}
905
906#[cfg(not(target_family = "wasm"))]
907fn format_from_output_path(output_file: &str) -> Result<ImageFormat, Error> {
908    let extension = Path::new(output_file)
909        .extension()
910        .and_then(|extension| extension.to_str())
911        .map(|extension| extension.to_ascii_lowercase());
912
913    match extension.as_deref() {
914        Some("gif") => Ok(ImageFormat::Gif),
915        Some("png") | Some("apng") => Ok(ImageFormat::Png),
916        Some("jpg") | Some("jpeg") => Ok(ImageFormat::Jpeg),
917        Some("bmp") => Ok(ImageFormat::Bmp),
918        Some("tif") | Some("tiff") => Ok(ImageFormat::Tiff),
919        Some("webp") => Ok(ImageFormat::Webp),
920        Some(extension) => Err(Box::new(ImgError::new_const(
921            ImgErrorKind::NoSupportFormat,
922            format!("unsupported output extension: {extension}"),
923        ))),
924        None => Err(Box::new(ImgError::new_const(
925            ImgErrorKind::InvalidParameter,
926            "output file has no extension".to_string(),
927        ))),
928    }
929}
930
931/// Converts an input image file to the format implied by `output_file`.
932///
933/// The output format is selected from the destination extension:
934/// `.gif` uses the GIF encoder, `.png` and `.apng` use the PNG/APNG encoder,
935/// `.jpg`/`.jpeg` use the JPEG encoder, `.bmp` uses the BMP encoder,
936/// `.tif`/`.tiff` use the TIFF encoder, and `.webp` uses the WebP encoder.
937/// Encoder-specific settings can be passed in `options`, for example JPEG
938/// `quality`, TIFF `compression`, WebP `quality` and `optimize`, or
939/// `exif = Ascii("copy")` to preserve source EXIF metadata.
940///
941/// # Examples
942/// ```no_run
943/// use std::collections::HashMap;
944/// use wml2::draw::convert;
945/// use wml2::metadata::DataMap;
946///
947/// let mut options = HashMap::new();
948/// options.insert("quality".to_string(), DataMap::UInt(90));
949///
950/// convert(
951///     "input.png".to_string(),
952///     "output.jpg".to_string(),
953///     Some(options),
954/// )?;
955/// # Ok::<(), Box<dyn std::error::Error>>(())
956/// ```
957#[cfg(not(target_family = "wasm"))]
958pub fn convert(
959    input_file: String,
960    output_file: String,
961    options: Option<HashMap<String, DataMap>>,
962) -> Result<(), Error> {
963    let format = format_from_output_path(&output_file)?;
964    let f = std::fs::File::create(&output_file)?;
965    let mut image = image_from_file(input_file)?;
966    let mut encode = EncodeOptions {
967        debug_flag: 0,
968        drawer: &mut image,
969        options,
970    };
971
972    image_writer(f, &mut encode, format)?;
973    Ok(())
974}
975
976/// Decodes an image from memory into an [`ImageBuffer`].
977///
978/// # Examples
979/// ```rust
980/// use wml2::draw::{ImageBuffer, image_load, image_to};
981/// use wml2::util::ImageFormat;
982///
983/// let mut source = ImageBuffer::from_buffer(2, 1, vec![255, 0, 0, 255, 0, 0, 255, 255]);
984/// let png = image_to(&mut source, ImageFormat::Png, None).unwrap();
985///
986/// let image = image_load(&png).unwrap();
987/// assert_eq!(image.width, 2);
988/// assert_eq!(image.height, 1);
989/// ```
990pub fn image_load(buffer: &[u8]) -> Result<ImageBuffer, Error> {
991    let mut ib = ImageBuffer::new();
992    let mut option = DecodeOptions {
993        debug_flag: 0,
994        drawer: &mut ib,
995    };
996    let mut reader = BytesReader::new(buffer);
997
998    image_decoder(&mut reader, &mut option)?;
999    Ok(ib)
1000}
1001
1002/// Decodes an in-memory image into a custom [`DrawCallback`].
1003///
1004/// # Examples
1005/// ```rust
1006/// use wml2::draw::{DecodeOptions, ImageBuffer, image_loader, image_to};
1007/// use wml2::util::ImageFormat;
1008///
1009/// let mut source = ImageBuffer::from_buffer(1, 1, vec![255, 0, 0, 255]);
1010/// let png = image_to(&mut source, ImageFormat::Png, None).unwrap();
1011///
1012/// let mut target = ImageBuffer::new();
1013/// let mut options = DecodeOptions {
1014///     debug_flag: 0,
1015///     drawer: &mut target,
1016/// };
1017/// image_loader(&png, &mut options).unwrap();
1018/// assert_eq!(target.width, 1);
1019/// assert_eq!(target.height, 1);
1020/// ```
1021pub fn image_loader(
1022    buffer: &[u8],
1023    option: &mut DecodeOptions,
1024) -> Result<Option<ImgWarnings>, Error> {
1025    let mut reader = BytesReader::new(buffer);
1026
1027    let r = image_decoder(&mut reader, option)?;
1028    Ok(r)
1029}
1030
1031/// Decodes an image stream into a custom [`DrawCallback`].
1032///
1033/// # Examples
1034/// ```rust
1035/// use std::io::Cursor;
1036/// use wml2::draw::{DecodeOptions, ImageBuffer, image_reader, image_to};
1037/// use wml2::util::ImageFormat;
1038///
1039/// let mut source = ImageBuffer::from_buffer(1, 1, vec![255, 0, 0, 255]);
1040/// let png = image_to(&mut source, ImageFormat::Png, None).unwrap();
1041///
1042/// let mut target = ImageBuffer::new();
1043/// let mut options = DecodeOptions {
1044///     debug_flag: 0,
1045///     drawer: &mut target,
1046/// };
1047/// image_reader(Cursor::new(png), &mut options).unwrap();
1048/// assert_eq!(target.width, 1);
1049/// assert_eq!(target.height, 1);
1050/// ```
1051#[cfg(not(target_family = "wasm"))]
1052pub fn image_reader<R: BufRead + Seek>(
1053    reader: R,
1054    option: &mut DecodeOptions,
1055) -> Result<Option<ImgWarnings>, Error> {
1056    let mut reader = StreamReader::new(reader);
1057
1058    let r = image_decoder(&mut reader, option)?;
1059    Ok(r)
1060}
1061
1062/// Encodes an image source to an arbitrary writer.
1063///
1064/// # Examples
1065/// ```rust
1066/// use wml2::draw::{EncodeOptions, ImageBuffer, image_writer};
1067/// use wml2::util::ImageFormat;
1068///
1069/// let mut image = ImageBuffer::from_buffer(1, 1, vec![255, 0, 0, 255]);
1070/// let mut options = EncodeOptions {
1071///     debug_flag: 0,
1072///     drawer: &mut image,
1073///     options: None,
1074/// };
1075/// let mut buffer = Vec::new();
1076/// image_writer(&mut buffer, &mut options, ImageFormat::Png).unwrap();
1077/// assert!(buffer.starts_with(&[0x89, b'P', b'N', b'G']));
1078/// ```
1079pub fn image_writer<W: Write>(
1080    mut writer: W,
1081    option: &mut EncodeOptions,
1082    format: ImageFormat,
1083) -> Result<Option<ImgWarnings>, Error> {
1084    let buffer = image_encoder(option, format)?;
1085    writer.write_all(&buffer)?;
1086    writer.flush()?;
1087    Ok(None)
1088}
1089
1090/// Detects the input format and dispatches to the matching decoder.
1091///
1092/// # Examples
1093/// ```rust
1094/// use bin_rs::reader::BytesReader;
1095/// use wml2::draw::{DecodeOptions, ImageBuffer, image_decoder, image_to};
1096/// use wml2::util::ImageFormat;
1097///
1098/// let mut source = ImageBuffer::from_buffer(1, 1, vec![255, 0, 0, 255]);
1099/// let png = image_to(&mut source, ImageFormat::Png, None).unwrap();
1100///
1101/// let mut reader = BytesReader::new(&png);
1102/// let mut target = ImageBuffer::new();
1103/// let mut options = DecodeOptions {
1104///     debug_flag: 0,
1105///     drawer: &mut target,
1106/// };
1107/// image_decoder(&mut reader, &mut options).unwrap();
1108/// assert_eq!(target.width, 1);
1109/// assert_eq!(target.height, 1);
1110/// ```
1111pub fn image_decoder<B: BinaryReader>(
1112    reader: &mut B,
1113    option: &mut DecodeOptions,
1114) -> Result<Option<ImgWarnings>, Error> {
1115    let current = reader.offset()?;
1116    let end = reader.seek(SeekFrom::End(0))?;
1117    reader.seek(SeekFrom::Start(current))?;
1118    let sample_len = usize::try_from((end - current).min(128)).map_err(|_| {
1119        Box::new(ImgError::new_const(
1120            ImgErrorKind::InvalidParameter,
1121            "input sample size overflow".to_string(),
1122        )) as Error
1123    })?;
1124    let buffer = reader.read_bytes_no_move(sample_len)?;
1125    let format = format_check(&buffer);
1126
1127    use crate::util::ImageFormat::*;
1128    match format {
1129        #[cfg(feature = "jpeg")]
1130        Jpeg => {
1131            return crate::jpeg::decoder::decode(reader, option);
1132        }
1133        #[cfg(feature = "bmp")]
1134        Bmp => {
1135            return crate::bmp::decoder::decode(reader, option);
1136        }
1137        #[cfg(feature = "ico")]
1138        Ico => {
1139            return crate::ico::decoder::decode(reader, option);
1140        }
1141        #[cfg(feature = "gif")]
1142        Gif => {
1143            return crate::gif::decoder::decode(reader, option);
1144        }
1145        #[cfg(feature = "png")]
1146        Png => {
1147            return crate::png::decoder::decode(reader, option);
1148        }
1149        #[cfg(feature = "webp")]
1150        Webp => {
1151            return crate::webp::decoder::decode(reader, option);
1152        }
1153        #[cfg(feature = "tiff")]
1154        Tiff => {
1155            return crate::tiff::decoder::decode(reader, option);
1156        }
1157        #[cfg(all(feature = "mag", not(feature = "noretoro")))]
1158        Mag => {
1159            return crate::mag::decoder::decode(reader, option);
1160        }
1161        #[cfg(all(feature = "maki", not(feature = "noretoro")))]
1162        Maki => {
1163            return crate::maki::decoder::decode(reader, option);
1164        }
1165        #[cfg(all(feature = "pi", not(feature = "noretoro")))]
1166        Pi => {
1167            return crate::pi::decoder::decode(reader, option);
1168        }
1169        #[cfg(all(feature = "pic", not(feature = "noretoro")))]
1170        Pic => {
1171            return crate::pic::decoder::decode(reader, option);
1172        }
1173        #[cfg(all(feature = "vsp", not(feature = "noretoro")))]
1174        Vsp => {
1175            return crate::vsp::decoder::decode(reader, option);
1176        }
1177        _ => {}
1178    }
1179
1180    #[cfg(all(feature = "pcd", not(feature = "noretoro")))]
1181    {
1182        let current = reader.seek(std::io::SeekFrom::Current(0))?;
1183        let pcd = (|| -> Result<bool, Error> {
1184            reader.seek(std::io::SeekFrom::Start(0x800))?;
1185            let mut id = [0u8; 7];
1186            reader.read_exact(&mut id)?;
1187            Ok(&id == b"PCD_IPI")
1188        })();
1189        reader.seek(std::io::SeekFrom::Start(current))?;
1190        if matches!(pcd, Ok(true)) {
1191            return crate::pcd::decoder::decode(reader, option);
1192        }
1193    }
1194
1195    Err(Box::new(ImgError::new_const(
1196        ImgErrorKind::NoSupportFormat,
1197        "This buffer can not decode".to_string(),
1198    )))
1199}
1200
1201/// Dispatches to the matching encoder for `format`.
1202///
1203/// # Examples
1204/// ```rust
1205/// use wml2::draw::{EncodeOptions, ImageBuffer, image_encoder};
1206/// use wml2::util::ImageFormat;
1207///
1208/// let mut image = ImageBuffer::from_buffer(1, 1, vec![255, 0, 0, 255]);
1209/// let mut options = EncodeOptions {
1210///     debug_flag: 0,
1211///     drawer: &mut image,
1212///     options: None,
1213/// };
1214/// let png = image_encoder(&mut options, ImageFormat::Png).unwrap();
1215/// assert!(png.starts_with(&[0x89, b'P', b'N', b'G']));
1216/// ```
1217pub fn image_encoder(option: &mut EncodeOptions, format: ImageFormat) -> Result<Vec<u8>, Error> {
1218    use crate::util::ImageFormat::*;
1219
1220    match format {
1221        #[cfg(feature = "gif")]
1222        Gif => {
1223            return crate::gif::encoder::encode(option);
1224        }
1225        #[cfg(feature = "bmp")]
1226        Bmp => {
1227            return crate::bmp::encoder::encode(option);
1228        }
1229        #[cfg(feature = "jpeg")]
1230        Jpeg => {
1231            return crate::jpeg::encoder::encode(option);
1232        }
1233        #[cfg(feature = "png")]
1234        Png => {
1235            return crate::png::encoder::encode(option);
1236        }
1237        #[cfg(feature = "tiff")]
1238        Tiff => {
1239            return crate::tiff::encoder::encode(option);
1240        }
1241        #[cfg(feature = "webp")]
1242        Webp => {
1243            return crate::webp::encoder::encode(option);
1244        }
1245        _ => Err(Box::new(ImgError::new_const(
1246            ImgErrorKind::NoSupportFormat,
1247            "This encoder no impl".to_string(),
1248        ))),
1249    }
1250}
1251
1252/// Encodes an [`ImageBuffer`] into a memory buffer.
1253///
1254/// This is a convenience wrapper around [`image_encoder`] for callers that
1255/// already use the built-in [`ImageBuffer`] instead of a custom
1256/// [`PickCallback`] implementation. `options` accepts the same encoder-specific
1257/// keys as [`EncodeOptions::options`], such as JPEG `quality`, TIFF
1258/// `compression`, WebP `quality`/`optimize`, or `exif`.
1259///
1260/// # Examples
1261/// ```rust
1262/// use wml2::draw::{ImageBuffer, image_to};
1263/// use wml2::util::ImageFormat;
1264///
1265/// let mut image = ImageBuffer::from_buffer(1, 1, vec![255, 0, 0, 255]);
1266/// let png = image_to(&mut image, ImageFormat::Png, None).unwrap();
1267/// assert!(png.starts_with(&[0x89, b'P', b'N', b'G']));
1268/// ```
1269pub fn image_to(
1270    image: &mut ImageBuffer,
1271    format: ImageFormat,
1272    options: Option<HashMap<String, DataMap>>,
1273) -> Result<Vec<u8>, Error> {
1274    let mut option = EncodeOptions {
1275        debug_flag: 0,
1276        drawer: image,
1277        options,
1278    };
1279    image_encoder(&mut option, format)
1280}