1 | //======================================================================== |
2 | // |
3 | // DCTStream.cc |
4 | // |
5 | // This file is licensed under the GPLv2 or later |
6 | // |
7 | // Copyright 2005 Jeff Muizelaar <jeff@infidigm.net> |
8 | // Copyright 2005-2010, 2012, 2017, 2020-2023 Albert Astals Cid <aacid@kde.org> |
9 | // Copyright 2009 Ryszard Trojnacki <rysiek@menel.com> |
10 | // Copyright 2010 Carlos Garcia Campos <carlosgc@gnome.org> |
11 | // Copyright 2011 Daiki Ueno <ueno@unixuser.org> |
12 | // Copyright 2011 Tomas Hoger <thoger@redhat.com> |
13 | // Copyright 2012, 2013 Thomas Freitag <Thomas.Freitag@alfa.de> |
14 | // Copyright 2017 Adrian Johnson <ajohnson@redneon.com> |
15 | // Copyright 2020 LluĂs Batlle i Rossell <viric@viric.name> |
16 | // |
17 | //======================================================================== |
18 | |
19 | #include "DCTStream.h" |
20 | |
21 | static void str_init_source(j_decompress_ptr cinfo) { } |
22 | |
23 | static boolean str_fill_input_buffer(j_decompress_ptr cinfo) |
24 | { |
25 | int c; |
26 | struct str_src_mgr *src = (struct str_src_mgr *)cinfo->src; |
27 | if (src->index == 0) { |
28 | c = 0xFF; |
29 | src->index++; |
30 | } else if (src->index == 1) { |
31 | c = 0xD8; |
32 | src->index++; |
33 | } else { |
34 | c = src->str->getChar(); |
35 | } |
36 | src->buffer = c; |
37 | src->pub.next_input_byte = &src->buffer; |
38 | src->pub.bytes_in_buffer = 1; |
39 | if (c != EOF) { |
40 | return TRUE; |
41 | } else { |
42 | return FALSE; |
43 | } |
44 | } |
45 | |
46 | static void str_skip_input_data(j_decompress_ptr cinfo, long num_bytes) |
47 | { |
48 | struct str_src_mgr *src = (struct str_src_mgr *)cinfo->src; |
49 | if (num_bytes > 0) { |
50 | while (num_bytes > (long)src->pub.bytes_in_buffer) { |
51 | num_bytes -= (long)src->pub.bytes_in_buffer; |
52 | str_fill_input_buffer(cinfo); |
53 | } |
54 | src->pub.next_input_byte += (size_t)num_bytes; |
55 | src->pub.bytes_in_buffer -= (size_t)num_bytes; |
56 | } |
57 | } |
58 | |
59 | static void str_term_source(j_decompress_ptr cinfo) { } |
60 | |
61 | DCTStream::DCTStream(Stream *strA, int colorXformA, Dict *dict, int recursion) : FilterStream(strA) |
62 | { |
63 | colorXform = colorXformA; |
64 | if (dict != nullptr) { |
65 | Object obj = dict->lookup(key: "Width" , recursion); |
66 | err.width = (obj.isInt() && obj.getInt() <= JPEG_MAX_DIMENSION) ? obj.getInt() : 0; |
67 | obj = dict->lookup(key: "Height" , recursion); |
68 | err.height = (obj.isInt() && obj.getInt() <= JPEG_MAX_DIMENSION) ? obj.getInt() : 0; |
69 | } else { |
70 | err.height = err.width = 0; |
71 | } |
72 | init(); |
73 | } |
74 | |
75 | DCTStream::~DCTStream() |
76 | { |
77 | jpeg_destroy_decompress(cinfo: &cinfo); |
78 | delete str; |
79 | } |
80 | |
81 | static void exitErrorHandler(jpeg_common_struct *error) |
82 | { |
83 | j_decompress_ptr cinfo = (j_decompress_ptr)error; |
84 | str_error_mgr *err = (struct str_error_mgr *)cinfo->err; |
85 | if (cinfo->err->msg_code == JERR_IMAGE_TOO_BIG && err->width != 0 && err->height != 0) { |
86 | cinfo->image_height = err->height; |
87 | cinfo->image_width = err->width; |
88 | } else { |
89 | longjmp(env: err->setjmp_buffer, val: 1); |
90 | } |
91 | } |
92 | |
93 | void DCTStream::init() |
94 | { |
95 | jpeg_std_error(err: &err.pub); |
96 | err.pub.error_exit = &exitErrorHandler; |
97 | src.pub.init_source = str_init_source; |
98 | src.pub.fill_input_buffer = str_fill_input_buffer; |
99 | src.pub.skip_input_data = str_skip_input_data; |
100 | src.pub.resync_to_restart = jpeg_resync_to_restart; |
101 | src.pub.term_source = str_term_source; |
102 | src.pub.bytes_in_buffer = 0; |
103 | src.pub.next_input_byte = nullptr; |
104 | src.str = str; |
105 | src.index = 0; |
106 | current = nullptr; |
107 | limit = nullptr; |
108 | |
109 | cinfo.err = &err.pub; |
110 | if (!setjmp(err.setjmp_buffer)) { |
111 | jpeg_create_decompress(&cinfo); |
112 | cinfo.src = (jpeg_source_mgr *)&src; |
113 | } |
114 | row_buffer = nullptr; |
115 | } |
116 | |
117 | void DCTStream::reset() |
118 | { |
119 | int row_stride; |
120 | |
121 | str->reset(); |
122 | |
123 | if (row_buffer) { |
124 | jpeg_destroy_decompress(cinfo: &cinfo); |
125 | init(); |
126 | } |
127 | |
128 | // JPEG data has to start with 0xFF 0xD8 |
129 | // but some pdf like the one on |
130 | // https://bugs.freedesktop.org/show_bug.cgi?id=3299 |
131 | // does have some garbage before that this seeks for |
132 | // the start marker... |
133 | bool startFound = false; |
134 | int c = 0, c2 = 0; |
135 | while (!startFound) { |
136 | if (!c) { |
137 | c = str->getChar(); |
138 | if (c == -1) { |
139 | error(category: errSyntaxError, pos: -1, msg: "Could not find start of jpeg data" ); |
140 | return; |
141 | } |
142 | if (c != 0xFF) { |
143 | c = 0; |
144 | } |
145 | } else { |
146 | c2 = str->getChar(); |
147 | if (c2 != 0xD8) { |
148 | c = 0; |
149 | c2 = 0; |
150 | } else { |
151 | startFound = true; |
152 | } |
153 | } |
154 | } |
155 | |
156 | if (!setjmp(err.setjmp_buffer)) { |
157 | if (jpeg_read_header(cinfo: &cinfo, TRUE) != JPEG_SUSPENDED) { |
158 | // figure out color transform |
159 | if (colorXform == -1 && !cinfo.saw_Adobe_marker) { |
160 | if (cinfo.num_components == 3) { |
161 | if (cinfo.saw_JFIF_marker) { |
162 | colorXform = 1; |
163 | } else if (cinfo.cur_comp_info[0]->component_id == 82 && cinfo.cur_comp_info[1]->component_id == 71 && cinfo.cur_comp_info[2]->component_id == 66) { // ASCII "RGB" |
164 | colorXform = 0; |
165 | } else { |
166 | colorXform = 1; |
167 | } |
168 | } else { |
169 | colorXform = 0; |
170 | } |
171 | } else if (cinfo.saw_Adobe_marker) { |
172 | colorXform = cinfo.Adobe_transform; |
173 | } |
174 | |
175 | switch (cinfo.num_components) { |
176 | case 3: |
177 | cinfo.jpeg_color_space = colorXform ? JCS_YCbCr : JCS_RGB; |
178 | break; |
179 | case 4: |
180 | cinfo.jpeg_color_space = colorXform ? JCS_YCCK : JCS_CMYK; |
181 | break; |
182 | } |
183 | |
184 | jpeg_start_decompress(cinfo: &cinfo); |
185 | |
186 | row_stride = cinfo.output_width * cinfo.output_components; |
187 | row_buffer = cinfo.mem->alloc_sarray((j_common_ptr)&cinfo, JPOOL_IMAGE, row_stride, 1); |
188 | } |
189 | } |
190 | } |
191 | |
192 | bool DCTStream::readLine() |
193 | { |
194 | if (cinfo.output_scanline < cinfo.output_height) { |
195 | if (!setjmp(err.setjmp_buffer)) { |
196 | if (!jpeg_read_scanlines(cinfo: &cinfo, scanlines: row_buffer, max_lines: 1)) { |
197 | return false; |
198 | } else { |
199 | current = &row_buffer[0][0]; |
200 | limit = &row_buffer[0][(cinfo.output_width - 1) * cinfo.output_components] + cinfo.output_components; |
201 | return true; |
202 | } |
203 | } else { |
204 | return false; |
205 | } |
206 | } else { |
207 | return false; |
208 | } |
209 | } |
210 | |
211 | int DCTStream::getChar() |
212 | { |
213 | if (current == limit) { |
214 | if (!readLine()) { |
215 | return EOF; |
216 | } |
217 | } |
218 | |
219 | return *current++; |
220 | } |
221 | |
222 | int DCTStream::getChars(int nChars, unsigned char *buffer) |
223 | { |
224 | for (int i = 0; i < nChars;) { |
225 | if (current == limit) { |
226 | if (!readLine()) { |
227 | return i; |
228 | } |
229 | } |
230 | intptr_t left = limit - current; |
231 | if (left + i > nChars) { |
232 | left = nChars - i; |
233 | } |
234 | memcpy(dest: buffer + i, src: current, n: left); |
235 | current += left; |
236 | i += static_cast<int>(left); |
237 | } |
238 | return nChars; |
239 | } |
240 | |
241 | int DCTStream::lookChar() |
242 | { |
243 | if (unlikely(current == nullptr)) { |
244 | return EOF; |
245 | } |
246 | return *current; |
247 | } |
248 | |
249 | GooString *DCTStream::getPSFilter(int psLevel, const char *indent) |
250 | { |
251 | GooString *s; |
252 | |
253 | if (psLevel < 2) { |
254 | return nullptr; |
255 | } |
256 | if (!(s = str->getPSFilter(psLevel, indent))) { |
257 | return nullptr; |
258 | } |
259 | s->append(str: indent)->append(str: "<< >> /DCTDecode filter\n" ); |
260 | return s; |
261 | } |
262 | |
263 | bool DCTStream::isBinary(bool last) const |
264 | { |
265 | return str->isBinary(last: true); |
266 | } |
267 | |