/
githubmirror
/
deno
Обзор
Документация
Войти
/
githubmirror
/
deno
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
ext/image/bitmap.rs
611 строк
17 KB
David Sherret
refactor: force a reason on clippy overrides (#32761)
16 мар 2026, 15:41
Не верифицирован
16 мар 2026, 15:41
39a07b2
Код
Авторство
О чём код?
// Copyright 2018-2026 the Deno authors. MIT license. use std::cell::OnceCell; use std::cell::RefCell; use std::io::BufReader; use std::io::Cursor; use deno_core::GarbageCollected; use deno_core::JsBuffer; use deno_core::op2; use deno_core::webidl::WebIdlInterfaceConverter; use image::DynamicImage; use image::ImageDecoder; use image::RgbaImage; use image::codecs::bmp::BmpDecoder; use image::codecs::gif::GifDecoder; use image::codecs::ico::IcoDecoder; use image::codecs::jpeg::JpegDecoder; use image::codecs::png::PngDecoder; use image::codecs::webp::WebPDecoder; use image::imageops::FilterType; use image::imageops::overlay; use image::metadata::Orientation; use crate::ImageError; use crate::image_ops::create_image_from_raw_bytes; use crate::image_ops::premultiply_alpha as process_premultiply_alpha; use crate::image_ops::to_srgb_from_icc_profile; use crate::image_ops::unpremultiply_alpha; #[derive(Debug, PartialEq)] enum ImageBitmapSource { Blob, ImageData, ImageBitmap, } #[derive(Debug, PartialEq)] enum ImageOrientation { FlipY, FromImage, } #[derive(Debug, PartialEq)] enum PremultiplyAlpha { Default, Premultiply, None, } #[derive(Debug, PartialEq)] enum ColorSpaceConversion { Default, None, } #[derive(Debug, PartialEq)] enum ResizeQuality { Pixelated, Low, Medium, High, } #[derive(Debug, PartialEq)] enum MimeType { NoMatch, Png, Jpeg, Gif, Bmp, Ico, Webp, } type DecodeBitmapDataReturn = (DynamicImage, u32, u32, Option<Orientation>, Option<Vec<u8>>); fn decode_bitmap_data( buf: &[u8], width: u32, height: u32, image_bitmap_source: &ImageBitmapSource, mime_type: MimeType, ) -> Result<DecodeBitmapDataReturn, ImageError> { let (image, width, height, orientation, icc_profile) = match image_bitmap_source { ImageBitmapSource::Blob => { // // About the animated image // > Blob .4 // > ... If this is an animated image, imageBitmap's bitmap data must only be taken from // > the default image of the animation (the one that the format defines is to be used when animation is // > not supported or is disabled), or, if there is no such image, the first frame of the animation. // https://html.spec.whatwg.org/multipage/imagebitmap-and-animations.html // // see also browser implementations: (The implementation of Gecko and WebKit is hard to read.) // https://source.chromium.org/chromium/chromium/src/+/bdbc054a6cabbef991904b5df9066259505cc686:third_party/blink/renderer/platform/image-decoders/image_decoder.h;l=175-189 // let (image, orientation, icc_profile) = match mime_type { MimeType::Png => { // If PngDecoder decodes an animated image, it returns the default image if one is set, or the first frame if not. let mut decoder = PngDecoder::new(BufReader::new(Cursor::new(buf))) .map_err(ImageError::image_error_to_invalid_image)?; let orientation = decoder.orientation()?; let icc_profile = decoder.icc_profile()?; ( DynamicImage::from_decoder(decoder) .map_err(ImageError::image_error_to_invalid_image)?, orientation, icc_profile, ) } MimeType::Jpeg => { let mut decoder = JpegDecoder::new(BufReader::new(Cursor::new(buf))) .map_err(ImageError::image_error_to_invalid_image)?; let orientation = decoder.orientation()?; let icc_profile = decoder.icc_profile()?; ( DynamicImage::from_decoder(decoder) .map_err(ImageError::image_error_to_invalid_image)?, orientation, icc_profile, ) } MimeType::Gif => { // The GifDecoder decodes the first frame. let mut decoder = GifDecoder::new(BufReader::new(Cursor::new(buf))) .map_err(ImageError::image_error_to_invalid_image)?; let orientation = decoder.orientation()?; let icc_profile = decoder.icc_profile()?; ( DynamicImage::from_decoder(decoder) .map_err(ImageError::image_error_to_invalid_image)?, orientation, icc_profile, ) } MimeType::Bmp => { let mut decoder = BmpDecoder::new(BufReader::new(Cursor::new(buf))) .map_err(ImageError::image_error_to_invalid_image)?; let orientation = decoder.orientation()?; let icc_profile = decoder.icc_profile()?; ( DynamicImage::from_decoder(decoder) .map_err(ImageError::image_error_to_invalid_image)?, orientation, icc_profile, ) } MimeType::Ico => { let mut decoder = IcoDecoder::new(BufReader::new(Cursor::new(buf))) .map_err(ImageError::image_error_to_invalid_image)?; let orientation = decoder.orientation()?; let icc_profile = decoder.icc_profile()?; ( DynamicImage::from_decoder(decoder) .map_err(ImageError::image_error_to_invalid_image)?, orientation, icc_profile, ) } MimeType::Webp => { // The WebPDecoder decodes the first frame. let mut decoder = WebPDecoder::new(BufReader::new(Cursor::new(buf))) .map_err(ImageError::image_error_to_invalid_image)?; let orientation = decoder.orientation()?; let icc_profile = decoder.icc_profile()?; ( DynamicImage::from_decoder(decoder) .map_err(ImageError::image_error_to_invalid_image)?, orientation, icc_profile, ) } // This pattern is unreachable due to current block is already checked by the ImageBitmapSource above. MimeType::NoMatch => unreachable!(), }; let width = image.width(); let height = image.height(); (image, width, height, Some(orientation), icc_profile) } ImageBitmapSource::ImageData => { // > 4.12.5.1.15 Pixel manipulation // > imagedata.data // > Returns the one-dimensional array containing the data in RGBA order, as integers in the range 0 to 255. // https://html.spec.whatwg.org/multipage/canvas.html#pixel-manipulation let image = match RgbaImage::from_raw(width, height, buf.into()) { Some(image) => image.into(), None => { return Err(ImageError::NotBigEnoughChunk(width, height)); } }; (image, width, height, None, None) } ImageBitmapSource::ImageBitmap => { let image = create_image_from_raw_bytes(width, height, buf)?; (image, width, height, None, None) } }; Ok((image, width, height, orientation, icc_profile)) } /// According to the spec, it's not clear how to handle the color space conversion. /// /// Therefore, if you interpret the specification description from the implementation and wpt results, it will be as follows. /// /// Let val be the value of the colorSpaceConversion member of options, and then run these substeps: /// 1. If val is "default", to convert to the sRGB color space. /// 2. If val is "none", to use the decoded image data as is. /// /// related issue in whatwg /// https://github.com/whatwg/html/issues/10578 /// /// reference in wpt /// https://github.com/web-platform-tests/wpt/blob/d575dc75ede770df322fbc5da3112dcf81f192ec/html/canvas/element/manual/imagebitmap/createImageBitmap-colorSpaceConversion.html#L18 /// https://wpt.live/html/canvas/element/manual/imagebitmap/createImageBitmap-colorSpaceConversion.html fn apply_color_space_conversion( image: DynamicImage, icc_profile: Option<Vec<u8>>, color_space_conversion: &ColorSpaceConversion, ) -> Result<DynamicImage, ImageError> { match color_space_conversion { // return the decoded image as is. ColorSpaceConversion::None => Ok(image), ColorSpaceConversion::Default => { to_srgb_from_icc_profile(image, icc_profile) } } } fn apply_premultiply_alpha( image: DynamicImage, image_bitmap_source: &ImageBitmapSource, premultiply_alpha: &PremultiplyAlpha, ) -> Result<DynamicImage, ImageError> { match premultiply_alpha { // 1. PremultiplyAlpha::Default => Ok(image), // https://html.spec.whatwg.org/multipage/canvas.html#convert-from-premultiplied // 2. PremultiplyAlpha::Premultiply => process_premultiply_alpha(image), // 3. PremultiplyAlpha::None => { // NOTE: It's not clear how to handle the case of ImageData. // https://issues.chromium.org/issues/339759426 // https://github.com/whatwg/html/issues/5365 if *image_bitmap_source == ImageBitmapSource::ImageData { return Ok(image); } unpremultiply_alpha(image) } } } #[derive(Debug, PartialEq)] struct ParsedArgs { resize_width: Option<u32>, resize_height: Option<u32>, sx: Option<i32>, sy: Option<i32>, sw: Option<i32>, sh: Option<i32>, image_orientation: ImageOrientation, premultiply_alpha: PremultiplyAlpha, color_space_conversion: ColorSpaceConversion, resize_quality: ResizeQuality, image_bitmap_source: ImageBitmapSource, mime_type: MimeType, } #[allow(clippy::too_many_arguments, reason = "all arguments are needed")] fn parse_args( sx: i32, sy: i32, sw: i32, sh: i32, image_orientation: u8, premultiply_alpha: u8, color_space_conversion: u8, resize_width: u32, resize_height: u32, resize_quality: u8, image_bitmap_source: u8, mime_type: u8, ) -> ParsedArgs { let resize_width = if resize_width == 0 { None } else { Some(resize_width) }; let resize_height = if resize_height == 0 { None } else { Some(resize_height) }; let sx = if sx == 0 { None } else { Some(sx) }; let sy = if sy == 0 { None } else { Some(sy) }; let sw = if sw == 0 { None } else { Some(sw) }; let sh = if sh == 0 { None } else { Some(sh) }; // Their unreachable wildcard patterns are validated in JavaScript-side. let image_orientation = match image_orientation { 0 => ImageOrientation::FromImage, 1 => ImageOrientation::FlipY, _ => unreachable!(), }; let premultiply_alpha = match premultiply_alpha { 0 => PremultiplyAlpha::Default, 1 => PremultiplyAlpha::Premultiply, 2 => PremultiplyAlpha::None, _ => unreachable!(), }; let color_space_conversion = match color_space_conversion { 0 => ColorSpaceConversion::Default, 1 => ColorSpaceConversion::None, _ => unreachable!(), }; let resize_quality = match resize_quality { 0 => ResizeQuality::Low, 1 => ResizeQuality::Pixelated, 2 => ResizeQuality::Medium, 3 => ResizeQuality::High, _ => unreachable!(), }; let image_bitmap_source = match image_bitmap_source { 0 => ImageBitmapSource::Blob, 1 => ImageBitmapSource::ImageData, 2 => ImageBitmapSource::ImageBitmap, _ => unreachable!(), }; let mime_type = match mime_type { 0 => MimeType::NoMatch, 1 => MimeType::Png, 2 => MimeType::Jpeg, 3 => MimeType::Gif, 4 => MimeType::Bmp, 5 => MimeType::Ico, 6 => MimeType::Webp, _ => unreachable!(), }; ParsedArgs { resize_width, resize_height, sx, sy, sw, sh, image_orientation, premultiply_alpha, color_space_conversion, resize_quality, image_bitmap_source, mime_type, } } #[op2] #[cppgc] #[allow(clippy::too_many_arguments, reason = "all arguments are needed")] pub(super) fn op_create_image_bitmap( #[buffer] buf: JsBuffer, width: u32, height: u32, sx: i32, sy: i32, sw: i32, sh: i32, image_orientation: u8, premultiply_alpha: u8, color_space_conversion: u8, resize_width: u32, resize_height: u32, resize_quality: u8, image_bitmap_source: u8, mime_type: u8, ) -> Result<ImageBitmap, ImageError> { let ParsedArgs { resize_width, resize_height, sx, sy, sw, sh, image_orientation, premultiply_alpha, color_space_conversion, resize_quality, image_bitmap_source, mime_type, } = parse_args( sx, sy, sw, sh, image_orientation, premultiply_alpha, color_space_conversion, resize_width, resize_height, resize_quality, image_bitmap_source, mime_type, ); // 6. Switch on image: let (image, width, height, orientation, icc_profile) = decode_bitmap_data(&buf, width, height, &image_bitmap_source, mime_type)?; // crop bitmap data // 2. #[rustfmt::skip] let source_rectangle: [[i32; 2]; 4] = if let (Some(sx), Some(sy), Some(sw), Some(sh)) = (sx, sy, sw, sh) { [ [sx, sy], [sx + sw, sy], [sx + sw, sy + sh], [sx, sy + sh] ] } else { [ [0, 0], [width as i32, 0], [width as i32, height as i32], [0, height as i32], ] }; /* * The cropping works differently than the spec specifies: * The spec states to create an infinite surface and place the top-left corner * of the image a 0,0 and crop based on sourceRectangle. * * We instead create a surface the size of sourceRectangle, and position * the image at the correct location, which is the inverse of the x & y of * sourceRectangle's top-left corner. */ let input_x = -(source_rectangle[0][0] as i64); let input_y = -(source_rectangle[0][1] as i64); let surface_width = (source_rectangle[1][0] - source_rectangle[0][0]) as u32; let surface_height = (source_rectangle[3][1] - source_rectangle[0][1]) as u32; // 3. let output_width = if let Some(resize_width) = resize_width { resize_width } else if let Some(resize_height) = resize_height { (surface_width * resize_height).div_ceil(surface_height) } else { surface_width }; // 4. let output_height = if let Some(resize_height) = resize_height { resize_height } else if let Some(resize_width) = resize_width { (surface_height * resize_width).div_ceil(surface_width) } else { surface_height }; // 5. let image = if !(width == surface_width && height == surface_height && input_x == 0 && input_y == 0) { let mut surface = DynamicImage::new(surface_width, surface_height, image.color()); overlay(&mut surface, &image, input_x, input_y); surface } else { image }; // 7. let filter_type = match resize_quality { ResizeQuality::Pixelated => FilterType::Nearest, ResizeQuality::Low => FilterType::Triangle, ResizeQuality::Medium => FilterType::CatmullRom, ResizeQuality::High => FilterType::Lanczos3, }; // should use resize_exact // https://github.com/image-rs/image/issues/1220#issuecomment-632060015 let mut image = image.resize_exact(output_width, output_height, filter_type); // 8. let image = match image_bitmap_source { ImageBitmapSource::Blob => { // Note: According to browser behavior and wpt results, if Exif contains image orientation, // it applies the rotation from it before following the value of imageOrientation. // This is not stated in the spec but in MDN currently. // https://github.com/mdn/content/pull/34366 // SAFETY: The orientation is always Some if the image is from a Blob. let orientation = orientation.unwrap(); DynamicImage::apply_orientation(&mut image, orientation); match image_orientation { ImageOrientation::FlipY => image.flipv(), ImageOrientation::FromImage => image, } } ImageBitmapSource::ImageData | ImageBitmapSource::ImageBitmap => { match image_orientation { ImageOrientation::FlipY => image.flipv(), ImageOrientation::FromImage => image, } } }; // 9. let image = apply_color_space_conversion(image, icc_profile, &color_space_conversion)?; // 10. let image = apply_premultiply_alpha(image, &image_bitmap_source, &premultiply_alpha)?; Ok(ImageBitmap { detached: Default::default(), data: RefCell::new(image), }) } pub struct ImageBitmap { pub detached: OnceCell<()>, pub data: RefCell<DynamicImage>, } // SAFETY: we're sure this can be GCed unsafe impl GarbageCollected for ImageBitmap { fn trace(&self, _visitor: &mut deno_core::v8::cppgc::Visitor) {} fn get_name(&self) -> &'static std::ffi::CStr { c"ImageBitmap" } } impl WebIdlInterfaceConverter for ImageBitmap { const NAME: &'static str = "ImageBitmap"; } #[op2] impl ImageBitmap { #[getter] fn width(&self) -> u32 { let data = self.data.borrow(); data.width() } #[getter] fn height(&self) -> u32 { let data = self.data.borrow(); data.height() } #[fast] fn close(&self) { let _ = self.detached.set(()); self .data .replace(DynamicImage::new(0, 0, image::ColorType::Rgba8)); } #[buffer] #[symbol("Deno_bitmapData")] fn getData(&self) -> Vec<u8> { let data = self.data.borrow(); data.as_bytes().to_vec() } } #[cfg(test)] mod tests { use super::*; #[test] fn test_parse_args() { let parsed_args = parse_args(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); assert_eq!( parsed_args, ParsedArgs { resize_width: None, resize_height: None, sx: None, sy: None, sw: None, sh: None, image_orientation: ImageOrientation::FromImage, premultiply_alpha: PremultiplyAlpha::Default, color_space_conversion: ColorSpaceConversion::Default, resize_quality: ResizeQuality::Low, image_bitmap_source: ImageBitmapSource::Blob, mime_type: MimeType::NoMatch, } ); } }