GCC Code Coverage Report


Directory: ./
File: image/image_serializer_xpm.cpp
Date: 2026-09-27 03:24:14
Exec Total Coverage
Lines: 2 181 1.1%
Functions: 1 10 10.0%
Branches: 0 313 0.0%

Line Branch Exec Source
1 /*
2 * Copyright (c) 2000-2022 Inria
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 *
8 * * Redistributions of source code must retain the above copyright notice,
9 * this list of conditions and the following disclaimer.
10 * * Redistributions in binary form must reproduce the above copyright notice,
11 * this list of conditions and the following disclaimer in the documentation
12 * and/or other materials provided with the distribution.
13 * * Neither the name of the ALICE Project-Team nor the names of its
14 * contributors may be used to endorse or promote products derived from this
15 * software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
21 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27 * POSSIBILITY OF SUCH DAMAGE.
28 *
29 * Contact: Bruno Levy
30 *
31 * https://www.inria.fr/fr/bruno-levy
32 *
33 * Inria,
34 * Domaine de Voluceau,
35 * 78150 Le Chesnay - Rocquencourt
36 * FRANCE
37 *
38 */
39
40 #include <geogram/image/image_serializer_xpm.h>
41 #include <geogram/image/image.h>
42 #include <geogram/basic/string.h>
43 #include <geogram/basic/logger.h>
44
45 #include <sstream>
46 #include <string.h>
47
48 namespace {
49 using namespace GEO;
50
51 /**
52 * \brief Converts an hexadecimal digit into an integer.
53 * \param[in] digit the character representation of the digit.
54 * \return the corresonding integer value, in [0,15].
55 */
56 ✗ inline int htoi(char digit) {
57 ✗ if(digit >= '0' && digit <= '9') {
58 ✗ return digit - '0';
59 }
60 ✗ if(digit >= 'a' && digit <= 'f') {
61 ✗ return digit - 'a' + 10;
62 }
63 ✗ if(digit >= 'A' && digit <= 'F') {
64 ✗ return digit - 'A' + 10;
65 }
66 ✗ Logger::err("Image")
67 << "XPM Image reader: hex digit to integer: invalid digit: \'"
68 ✗ << digit << "\'" << std::endl;
69 ✗ abort();
70 }
71
72 /**
73 * \brief Decodes an XPM colormap entry.
74 * \param[in] colormap_entry the string with the colormap entry.
75 * \param[out] colorcell the corresponding color.
76 */
77 ✗ bool decode_colormap_entry(
78 const char* colormap_entry, Colormap::ColorCell& colorcell
79 ) {
80 ✗ const char* colorcode = strstr(colormap_entry, "c #");
81 ✗ if(colorcode == nullptr) {
82 ✗ if(strstr(colormap_entry, "None") != nullptr) {
83 ✗ colorcell = Colormap::ColorCell(0,0,0,0);
84 ✗ return true;
85 } else {
86 ✗ Logger::err("Image")
87 ✗ << "XPM Image reader: Colormap entry without any color"
88 ✗ << std::endl;
89 ✗ Logger::err("Image")
90 ✗ << " entry = \'" << colormap_entry << "\'" << std::endl;
91 ✗ return false;
92 }
93 }
94 ✗ colorcode += 3;
95
96 Memory::byte r,g,b,a;
97
98 ✗ if(strlen(colorcode) == 12) {
99 ✗ r = Memory::byte(16 * htoi(colorcode[0]) + htoi(colorcode[1]));
100 ✗ g = Memory::byte(16 * htoi(colorcode[4]) + htoi(colorcode[5]));
101 ✗ b = Memory::byte(16 * htoi(colorcode[8]) + htoi(colorcode[9]));
102 ✗ a = 255;
103 } else {
104 ✗ r = Memory::byte(16 * htoi(colorcode[0]) + htoi(colorcode[1]));
105 ✗ g = Memory::byte(16 * htoi(colorcode[2]) + htoi(colorcode[3]));
106 ✗ b = Memory::byte(16 * htoi(colorcode[4]) + htoi(colorcode[5]));
107 ✗ a = 255;
108 }
109 ✗ colorcell = Colormap::ColorCell(r,g,b,a);
110 ✗ return true;
111 }
112
113 }
114
115 /******************************************************************************/
116
117 namespace GEO {
118
119 ✗ Image* ImageSerializer_xpm::serialize_read_from_stream(
120 std::istream& stream
121 ) {
122 ✗ return serialize_read_static(stream);
123 }
124
125 ✗ Image* ImageSerializer_xpm::create_image_from_xpm_data(const char* s) {
126 ✗ std::istringstream in(s);
127 ✗ return serialize_read_static(in);
128 ✗ }
129
130 ✗ Image* ImageSerializer_xpm::serialize_read_static(std::istream& stream) {
131
132 ✗ Image* result = nullptr;
133
134 int num_colors;
135 int chars_per_pixels;
136 int width;
137 int height;
138
139 // *********************** header
140 {
141 ✗ char* header = next_xpm_data(stream);
142 ✗ if(header == nullptr) {
143 ✗ Logger::err("Image")
144 ✗ << "XPM image input: unexpected end of file" << std::endl;
145 ✗ return nullptr;
146 }
147 ✗ std::istringstream in(header);
148 ✗ in >> width >> height >> num_colors >> chars_per_pixels;
149 ✗ if(num_colors > 1024) {
150 ✗ Logger::err("Image")
151 ✗ << "XPM image input: too many colors ("
152 << num_colors
153 ✗ << ")" << std::endl;
154 ✗ Logger::err("Image")
155 ✗ << " should not be greater than 1024" << std::endl;
156 ✗ return nullptr;
157 }
158
159 ✗ switch(chars_per_pixels) {
160 ✗ case 1:
161 ✗ result=read_xpm_1_byte_per_pixel(
162 width, height, num_colors, stream
163 );
164 ✗ break;
165 ✗ case 2:
166 ✗ result=read_xpm_2_bytes_per_pixel(
167 width, height, num_colors, stream
168 );
169 ✗ break;
170 ✗ default:
171 ✗ Logger::err("Image")
172 ✗ << "XPM image input: invalid chars per pixels ("
173 ✗ << chars_per_pixels << ")" << std::endl;
174 ✗ Logger::err("Image") << " should be 2" << std::endl;
175 ✗ return nullptr;
176 }
177
178 // Convert colormapped to RGBA.
179 // We do that because texturing functions are not implemented
180 // for colormapped.
181 // TODO: move function to Image library.
182 Image* result_rgba = new Image(
183 Image::RGBA, Image::BYTE, result->width(), result->height()
184 ✗ );
185 ✗ for(index_t y=0; y<result->height(); ++y) {
186 ✗ for(index_t x=0; x<result->width(); ++x) {
187 ✗ index_t c = index_t(*result->pixel_base(x,y));
188 ✗ result_rgba->pixel_base(x,y)[0] =
189 ✗ result->colormap()->color_cell(c).r();
190 ✗ result_rgba->pixel_base(x,y)[1] =
191 ✗ result->colormap()->color_cell(c).g();
192 ✗ result_rgba->pixel_base(x,y)[2] =
193 ✗ result->colormap()->color_cell(c).b();
194 ✗ result_rgba->pixel_base(x,y)[3] =
195 ✗ result->colormap()->color_cell(c).a();
196 }
197 }
198 ✗ delete result;
199
200 ✗ return result_rgba;
201 ✗ }
202 }
203
204 ✗ Image* ImageSerializer_xpm::read_xpm_2_bytes_per_pixel(
205 int width, int height, int num_colors, std::istream& stream
206 ) {
207
208 // Converts a two-digit XPM color code into
209 // a color index.
210 static int conv_table[256][256];
211
212 // For checking, put a negative value to
213 // detect invalid color codes.
214 ✗ for(int k1=0; k1 < 256; k1++) {
215 ✗ for(int k2=0; k2 < 256; k2++) {
216 ✗ conv_table[k1][k2] = -1;
217 }
218 }
219
220 // ********************** colormap
221
222 typedef Numeric::uint8 byte;
223
224 ✗ Colormap* colormap = new Colormap(index_t(num_colors));
225
226 ✗ for(int entry_num=0; entry_num<num_colors; entry_num++) {
227 ✗ char* entry = next_xpm_data(stream);
228 ✗ if(entry == nullptr) {
229 ✗ Logger::err("Image")
230 ✗ << "XPM Image reader: Unexpected end of file"
231 ✗ << std::endl;
232 ✗ delete colormap;
233 ✗ return nullptr;
234 }
235
236 ✗ int key1 = entry[0];
237 ✗ int key2 = entry[1];
238
239 ✗ Colormap::ColorCell cell;
240 ✗ if(!decode_colormap_entry(entry, cell)) {
241 ✗ return nullptr;
242 }
243
244 ✗ colormap-> color_cell(index_t(entry_num)) = cell;
245 ✗ conv_table[key1][key2] = (unsigned char)entry_num;
246 }
247
248 // ****************** image
249
250 Image* result = new Image(
251 Image::INDEXED, Image::BYTE, index_t(width), index_t(height)
252 ✗ );
253 ✗ result-> set_colormap(colormap);
254
255 ✗ for(int y=0; y<height; y++) {
256 ✗ char* scan_line = next_xpm_data(stream);
257 ✗ if(scan_line == nullptr) {
258 ✗ Logger::err("Image")
259 ✗ << "XPM Image reader: Unexpected end of file"
260 ✗ << std::endl;
261 ✗ delete result;
262 ✗ return nullptr;
263 }
264 ✗ for(int x=0; x<width; x++) {
265 ✗ int key1 = scan_line[2*x];
266 ✗ int key2 = scan_line[2*x+1];
267 ✗ int color_index = conv_table[key1][key2];
268 ✗ if(color_index < 0 || color_index > num_colors) {
269 ✗ Logger::err("Image")
270 ✗ << "XPM Image reader: Invalid color index in image"
271 ✗ << std::endl;
272 ✗ delete result;
273 ✗ return nullptr;
274 }
275 ✗ result-> base_mem()[y * width + x] = byte(color_index);
276 }
277 }
278
279 ✗ return result;
280 }
281
282
283 ✗ Image* ImageSerializer_xpm::read_xpm_1_byte_per_pixel(
284 int width, int height, int num_colors, std::istream& stream
285 ) {
286
287 // Converts a two-digit XPM color code into
288 // a color index.
289 static int conv_table[256];
290
291 // For checking, put a negative value to
292 // detect invalid color codes.
293 ✗ for(int k1=0; k1 < 256; k1++) {
294 ✗ conv_table[k1] = -1;
295 }
296
297 // ********************* colormap
298
299 typedef Numeric::uint8 byte;
300
301 ✗ Colormap* colormap = new Colormap(index_t(num_colors));
302
303 ✗ for(int entry_num=0; entry_num<num_colors; entry_num++) {
304 ✗ char* entry = next_xpm_data(stream);
305 ✗ if(entry == nullptr) {
306 ✗ Logger::err("Image")
307 ✗ << "XPM Image reader: Unexpected end of file"
308 ✗ << std::endl;
309 ✗ delete colormap;
310 ✗ return nullptr;
311 }
312
313 ✗ int key1 = entry[0];
314
315 ✗ Colormap::ColorCell cell;
316 ✗ if(!decode_colormap_entry(entry, cell)) {
317 ✗ return nullptr;
318 }
319 ✗ colormap-> color_cell(index_t(entry_num)) = cell;
320 ✗ conv_table[key1] = (unsigned char)entry_num;
321 }
322
323 // ********************* image
324
325 Image* result = new Image(
326 Image::INDEXED, Image::BYTE, index_t(width), index_t(height)
327 ✗ );
328 ✗ result-> set_colormap(colormap);
329
330 ✗ for(int y=0; y<height; y++) {
331 ✗ char* scan_line = next_xpm_data(stream);
332 ✗ if(scan_line == nullptr) {
333 ✗ Logger::err("Image")
334 ✗ << "XPM Image reader: Unexpected end of file"
335 ✗ << std::endl;
336 ✗ delete result;
337 ✗ return nullptr;
338 }
339 ✗ for(int x=0; x<width; x++) {
340 ✗ int key1 = scan_line[x];
341 ✗ int color_index = conv_table[key1];
342 ✗ if(color_index < 0 || color_index > num_colors) {
343 ✗ Logger::err("Image")
344 ✗ << "XPM Image reader: Invalid color index in image"
345 ✗ << std::endl;
346 ✗ delete result;
347 ✗ return nullptr;
348 }
349 ✗ result-> base_mem()[y * width + x] = byte(color_index);
350 }
351 }
352
353 ✗ return result;
354 }
355
356 ✗ bool ImageSerializer_xpm::binary() const {
357 ✗ return false;
358 }
359
360 ✗ char* ImageSerializer_xpm::next_xpm_data(std::istream& input) {
361 static char line_buffer[4096];
362 ✗ char* result = nullptr;
363 ✗ bool found = false;
364 ✗ while(!found && !input.eof()) {
365 ✗ input.getline(line_buffer,4096);
366 ✗ char* p1 = strchr(line_buffer,'\"');
367 ✗ char* p2 = strchr(line_buffer + 1, '\"');
368 ✗ found = (p1 != nullptr && p2 != nullptr);
369 ✗ if(found) {
370 ✗ result = p1 + 1;
371 ✗ *p2 = '\0';
372 }
373 }
374 ✗ return result;
375 }
376
377 510 bool ImageSerializer_xpm::read_supported() const {
378 510 return true;
379 }
380
381 /**********************************************************************/
382
383 }
384