1 // This Source Code Form is subject to the terms of the Mozilla Public
2 // License, v. 2.0. If a copy of the MPL was not distributed with this
3 // file, You can obtain one at http://mozilla.org/MPL/2.0/.
4
5 use std::{fs, path};
6
7 use log::warn;
8
9 use crate::prelude::*;
10
11
12 pub struct Image {
13 pub data: ImageData,
14 pub size: ScreenSize,
15 }
16
17 pub enum ImageData {
18 RGB(Vec<u8>),
19 RGBA(Vec<u8>),
20 }
21
load_raster( format: usvg::ImageFormat, data: &usvg::ImageData, opt: &Options, ) -> Option<Image>22 pub fn load_raster(
23 format: usvg::ImageFormat,
24 data: &usvg::ImageData,
25 opt: &Options,
26 ) -> Option<Image> {
27 let img = _load_raster(format, data, opt);
28
29 if img.is_none() {
30 match data {
31 usvg::ImageData::Path(ref path) => {
32 let path = get_abs_path(path, opt);
33 warn!("Failed to load an external image: {:?}.", path);
34 }
35 usvg::ImageData::Raw(_) => {
36 warn!("Failed to load an embedded image.");
37 }
38 }
39 }
40
41 img
42 }
43
_load_raster( format: usvg::ImageFormat, data: &usvg::ImageData, opt: &Options, ) -> Option<Image>44 fn _load_raster(
45 format: usvg::ImageFormat,
46 data: &usvg::ImageData,
47 opt: &Options,
48 ) -> Option<Image> {
49 debug_assert!(format != usvg::ImageFormat::SVG);
50
51 match data {
52 usvg::ImageData::Path(ref path) => {
53 let path = get_abs_path(path, opt);
54 let data = fs::read(path).ok()?;
55
56 if format == usvg::ImageFormat::JPEG {
57 read_jpeg(&data)
58 } else {
59 read_png(&data)
60 }
61 }
62 usvg::ImageData::Raw(ref data) => {
63 if format == usvg::ImageFormat::JPEG {
64 read_jpeg(data)
65 } else {
66 read_png(data)
67 }
68 }
69 }
70 }
71
read_png(data: &[u8]) -> Option<Image>72 fn read_png(data: &[u8]) -> Option<Image> {
73 let decoder = png::Decoder::new(data);
74 let (info, mut reader) = decoder.read_info().ok()?;
75
76 match info.color_type {
77 png::ColorType::RGB | png::ColorType::RGBA => {}
78 _ => return None,
79 }
80
81 let size = ScreenSize::new(info.width, info.height)?;
82
83 let mut img_data = vec![0; info.buffer_size()];
84 reader.next_frame(&mut img_data).ok()?;
85
86 let data = match info.color_type {
87 png::ColorType::RGB => ImageData::RGB(img_data),
88 png::ColorType::RGBA => ImageData::RGBA(img_data),
89 png::ColorType::Grayscale => {
90 let mut rgb_data = Vec::with_capacity(img_data.len() * 3);
91 for gray in img_data {
92 rgb_data.push(gray);
93 rgb_data.push(gray);
94 rgb_data.push(gray);
95 }
96
97 ImageData::RGB(rgb_data)
98 }
99 png::ColorType::GrayscaleAlpha => {
100 let mut rgba_data = Vec::with_capacity(img_data.len() * 2);
101 for slice in img_data.chunks(2) {
102 let gray = slice[0];
103 let alpha = slice[1];
104 rgba_data.push(gray);
105 rgba_data.push(gray);
106 rgba_data.push(gray);
107 rgba_data.push(alpha);
108 }
109
110 ImageData::RGBA(rgba_data)
111 }
112 _ => return None,
113 };
114
115 Some(Image {
116 data,
117 size,
118 })
119 }
120
read_jpeg(data: &[u8]) -> Option<Image>121 fn read_jpeg(data: &[u8]) -> Option<Image> {
122 let mut decoder = jpeg_decoder::Decoder::new(data);
123 let img_data = decoder.decode().ok()?;
124 let info = decoder.info()?;
125
126 let size = ScreenSize::new(info.width as u32, info.height as u32)?;
127
128 let data = match info.pixel_format {
129 jpeg_decoder::PixelFormat::RGB24 => ImageData::RGB(img_data),
130 jpeg_decoder::PixelFormat::L8 => {
131 let mut rgb_data = Vec::with_capacity(img_data.len() * 3);
132 for gray in img_data {
133 rgb_data.push(gray);
134 rgb_data.push(gray);
135 rgb_data.push(gray);
136 }
137
138 ImageData::RGB(rgb_data)
139 }
140 _ => return None,
141 };
142
143 Some(Image {
144 data,
145 size,
146 })
147 }
148
load_sub_svg( data: &usvg::ImageData, opt: &Options, ) -> Option<(usvg::Tree, Options)>149 pub fn load_sub_svg(
150 data: &usvg::ImageData,
151 opt: &Options,
152 ) -> Option<(usvg::Tree, Options)> {
153 let mut sub_opt = Options {
154 usvg: usvg::Options {
155 path: None,
156 dpi: opt.usvg.dpi,
157 font_family: opt.usvg.font_family.clone(),
158 font_size: opt.usvg.font_size,
159 languages: opt.usvg.languages.clone(),
160 shape_rendering: opt.usvg.shape_rendering,
161 text_rendering: opt.usvg.text_rendering,
162 image_rendering: opt.usvg.image_rendering,
163 keep_named_groups: false,
164 },
165 fit_to: FitTo::Original,
166 background: None,
167 };
168
169 let tree = match data {
170 usvg::ImageData::Path(ref path) => {
171 let path = get_abs_path(path, opt);
172 sub_opt.usvg.path = Some(path.clone());
173 usvg::Tree::from_file(path, &sub_opt.usvg).ok()?
174 }
175 usvg::ImageData::Raw(ref data) => {
176 usvg::Tree::from_data(data, &sub_opt.usvg).ok()?
177 }
178 };
179
180 sanitize_sub_svg(&tree);
181
182 Some((tree, sub_opt))
183 }
184
sanitize_sub_svg( tree: &usvg::Tree, )185 fn sanitize_sub_svg(
186 tree: &usvg::Tree,
187 ) {
188 // Remove all Image nodes.
189 //
190 // The referenced SVG image cannot have any 'image' elements by itself.
191 // Not only recursive. Any. Don't know why.
192
193 // TODO: implement drain or something to the rctree.
194 let mut changed = true;
195 while changed {
196 changed = false;
197
198 for mut node in tree.root().descendants() {
199 let mut rm = false;
200 if let usvg::NodeKind::Image(_) = *node.borrow() {
201 rm = true;
202 };
203
204 if rm {
205 node.detach();
206 changed = true;
207 break;
208 }
209 }
210 }
211 }
212
prepare_sub_svg_geom( view_box: usvg::ViewBox, img_size: ScreenSize, ) -> (usvg::Transform, Option<Rect>)213 pub fn prepare_sub_svg_geom(
214 view_box: usvg::ViewBox,
215 img_size: ScreenSize,
216 ) -> (usvg::Transform, Option<Rect>) {
217 let r = view_box.rect;
218
219 let new_size = utils::apply_view_box(&view_box, img_size);
220
221 let (tx, ty, clip) = if view_box.aspect.slice {
222 let (dx, dy) = utils::aligned_pos(
223 view_box.aspect.align,
224 0.0, 0.0, new_size.width() as f64 - r.width(), new_size.height() as f64 - r.height(),
225 );
226
227 (r.x() - dx, r.y() - dy, Some(r))
228 } else {
229 let (dx, dy) = utils::aligned_pos(
230 view_box.aspect.align,
231 r.x(), r.y(), r.width() - new_size.width() as f64, r.height() - new_size.height() as f64,
232 );
233
234 (dx, dy, None)
235 };
236
237 let sx = new_size.width() as f64 / img_size.width() as f64;
238 let sy = new_size.height() as f64 / img_size.height() as f64;
239 let ts = usvg::Transform::new(sx, 0.0, 0.0, sy, tx, ty);
240
241 (ts, clip)
242 }
243
image_rect( view_box: &usvg::ViewBox, img_size: ScreenSize, ) -> Rect244 pub fn image_rect(
245 view_box: &usvg::ViewBox,
246 img_size: ScreenSize,
247 ) -> Rect {
248 let new_size = utils::apply_view_box(view_box, img_size);
249 let (x, y) = utils::aligned_pos(
250 view_box.aspect.align,
251 view_box.rect.x(),
252 view_box.rect.y(),
253 view_box.rect.width() - new_size.width() as f64,
254 view_box.rect.height() - new_size.height() as f64,
255 );
256
257 new_size.to_size().to_rect(x, y)
258 }
259
get_abs_path( rel_path: &path::Path, opt: &Options, ) -> path::PathBuf260 fn get_abs_path(
261 rel_path: &path::Path,
262 opt: &Options,
263 ) -> path::PathBuf {
264 match opt.usvg.path {
265 Some(ref path) => path.parent().unwrap().join(rel_path),
266 None => rel_path.into(),
267 }
268 }
269