1use std::hash::{Hash, Hasher};
2use std::sync::{Arc, OnceLock};
3
4use ecow::eco_format;
5use image::{DynamicImage, EncodableLayout, GenericImageView, Rgba};
6use krilla::image::{BitsPerComponent, CustomImage, ImageColorspace};
7use krilla::pdf::PdfDocument;
8use krilla::surface::Surface;
9use krilla_svg::{SurfaceExt, SvgSettings};
10use typst_library::diag::{At, SourceResult};
11use typst_library::foundations::Smart;
12use typst_library::layout::{Abs, Angle, Ratio, Size, Transform};
13use typst_library::visualize::{
14 ExchangeFormat, Image, ImageKind, ImageScaling, PdfImage, RasterFormat, RasterImage,
15};
16use typst_syntax::Span;
17use typst_utils::defer;
18
19use crate::convert::{FrameContext, GlobalContext};
20use crate::tags;
21use crate::util::{SizeExt, TransformExt};
22
23#[typst_macros::time(name = "handle image")]
24pub(crate) fn handle_image(
25 gc: &mut GlobalContext,
26 fc: &mut FrameContext,
27 image: &Image,
28 size: Size,
29 surface: &mut Surface,
30 span: Span,
31) -> SourceResult<()> {
32 surface.push_transform(&fc.state().transform().to_krilla());
33 surface.set_location(span.into_raw());
34 let mut surface = defer(surface, |s| {
35 s.pop();
36 s.reset_location();
37 });
38
39 let interpolate = image.scaling() == Smart::Custom(ImageScaling::Smooth);
40
41 gc.image_spans.insert(span);
42
43 let mut handle = tags::image(gc, fc, &mut surface, image, size);
44 let surface = handle.surface();
45
46 match image.kind() {
47 ImageKind::Raster(raster) => {
48 let (exif_transform, new_size) = exif_transform(raster, size);
49 surface.push_transform(&exif_transform.to_krilla());
50 let mut surface = defer(surface, |s| s.pop());
51
52 let image = convert_raster(raster.clone(), interpolate)
53 .map_err(|err| eco_format!("failed to process image ({err})"))
54 .at(span)?;
55
56 if !gc.image_to_spans.contains_key(&image) {
57 gc.image_to_spans.insert(image.clone(), span);
58 }
59
60 if let Some(size) = new_size.to_krilla() {
61 surface.draw_image(image, size);
62 }
63 }
64 ImageKind::Svg(svg) => {
65 if let Some(size) = size.to_krilla() {
66 surface.draw_svg(
67 svg.tree(),
68 size,
69 SvgSettings { embed_text: true, ..Default::default() },
70 );
71 }
72 }
73 ImageKind::Pdf(pdf) => {
74 if let Some(size) = size.to_krilla() {
75 surface.draw_pdf_page(&convert_pdf(pdf), size, pdf.page_index());
76 }
77 }
78 }
79
80 Ok(())
81}
82
83struct Repr {
84 raster: RasterImage,
86 alpha_channel: OnceLock<Option<Vec<u8>>>,
88 actual_dynamic: OnceLock<Arc<DynamicImage>>,
92}
93
94#[derive(Clone)]
96struct PdfRasterImage(Arc<Repr>);
97
98impl PdfRasterImage {
99 pub fn new(raster: RasterImage) -> Self {
100 Self(Arc::new(Repr {
101 raster,
102 alpha_channel: OnceLock::new(),
103 actual_dynamic: OnceLock::new(),
104 }))
105 }
106}
107
108impl Hash for PdfRasterImage {
109 fn hash<H: Hasher>(&self, state: &mut H) {
110 self.0.raster.hash(state);
113 }
114}
115
116impl CustomImage for PdfRasterImage {
117 fn color_channel(&self) -> &[u8] {
118 self.0
119 .actual_dynamic
120 .get_or_init(|| {
121 let dynamic = self.0.raster.dynamic();
122 let channel_count = dynamic.color().channel_count();
123
124 match (dynamic.as_ref(), channel_count) {
125 (DynamicImage::ImageLuma8(_), _) => dynamic.clone(),
127 (DynamicImage::ImageRgb8(_), _) => dynamic.clone(),
128 (_, 1 | 2) => Arc::new(DynamicImage::ImageLuma8(dynamic.to_luma8())),
130 _ => Arc::new(DynamicImage::ImageRgb8(dynamic.to_rgb8())),
132 }
133 })
134 .as_bytes()
135 }
136
137 fn alpha_channel(&self) -> Option<&[u8]> {
138 self.0
139 .alpha_channel
140 .get_or_init(|| {
141 self.0.raster.dynamic().color().has_alpha().then(|| {
142 self.0
143 .raster
144 .dynamic()
145 .pixels()
146 .map(|(_, _, Rgba([_, _, _, a]))| a)
147 .collect()
148 })
149 })
150 .as_ref()
151 .map(|v| &**v)
152 }
153
154 fn bits_per_component(&self) -> BitsPerComponent {
155 BitsPerComponent::Eight
156 }
157
158 fn size(&self) -> (u32, u32) {
159 (self.0.raster.width(), self.0.raster.height())
160 }
161
162 fn icc_profile(&self) -> Option<&[u8]> {
163 if matches!(
164 self.0.raster.dynamic().as_ref(),
165 DynamicImage::ImageLuma8(_)
166 | DynamicImage::ImageLumaA8(_)
167 | DynamicImage::ImageRgb8(_)
168 | DynamicImage::ImageRgba8(_)
169 ) {
170 self.0.raster.icc().map(|b| b.as_bytes())
171 } else {
172 None
175 }
176 }
177
178 fn color_space(&self) -> ImageColorspace {
179 if self.0.raster.dynamic().color().has_color() {
181 ImageColorspace::Rgb
182 } else {
183 ImageColorspace::Luma
184 }
185 }
186}
187
188#[comemo::memoize]
189fn convert_raster(
190 raster: RasterImage,
191 interpolate: bool,
192) -> Result<krilla::image::Image, String> {
193 if let RasterFormat::Exchange(ExchangeFormat::Jpg) = raster.format() {
194 let image_data: Arc<dyn AsRef<[u8]> + Send + Sync> =
195 Arc::new(raster.data().clone());
196 let icc_profile = raster.icc().map(|i| {
197 let i: Arc<dyn AsRef<[u8]> + Send + Sync> = Arc::new(i.clone());
198 i
199 });
200
201 krilla::image::Image::from_jpeg_with_icc(
202 image_data.into(),
203 icc_profile.map(|i| i.into()),
204 interpolate,
205 )
206 } else {
207 krilla::image::Image::from_custom(PdfRasterImage::new(raster), interpolate)
208 }
209}
210
211#[comemo::memoize]
212fn convert_pdf(pdf: &PdfImage) -> PdfDocument {
213 PdfDocument::new(pdf.document().pdf().clone())
214}
215
216fn exif_transform(image: &RasterImage, size: Size) -> (Transform, Size) {
217 if image.format() != RasterFormat::Exchange(ExchangeFormat::Jpg) {
221 return (Transform::identity(), size);
222 }
223
224 let base = |hp: bool, vp: bool, mut base_ts: Transform, size: Size| {
225 if hp {
226 base_ts = base_ts.pre_concat(
228 Transform::scale(-Ratio::one(), Ratio::one())
229 .pre_concat(Transform::translate(-size.x, Abs::zero())),
230 )
231 }
232
233 if vp {
234 base_ts = base_ts.pre_concat(
236 Transform::scale(Ratio::one(), -Ratio::one())
237 .pre_concat(Transform::translate(Abs::zero(), -size.y)),
238 )
239 }
240
241 base_ts
242 };
243
244 let no_flipping =
245 |hp: bool, vp: bool| (base(hp, vp, Transform::identity(), size), size);
246
247 let with_flipping = |hp: bool, vp: bool| {
248 let base_ts = Transform::rotate_at(Angle::deg(90.0), Abs::zero(), Abs::zero())
249 .pre_concat(Transform::scale(Ratio::one(), -Ratio::one()));
250 let inv_size = Size::new(size.y, size.x);
251 (base(hp, vp, base_ts, inv_size), inv_size)
252 };
253
254 match image.exif_rotation() {
255 Some(2) => no_flipping(true, false),
256 Some(3) => no_flipping(true, true),
257 Some(4) => no_flipping(false, true),
258 Some(5) => with_flipping(false, false),
259 Some(6) => with_flipping(false, true),
260 Some(7) => with_flipping(true, true),
261 Some(8) => with_flipping(true, false),
262 _ => no_flipping(false, false),
263 }
264}