画像の簡易入出力とテストプログラムを追加
[cuda.git] / libutils / ImageIO.cpp
1 /*
2         Copyright (C) 2012  fmaj7b5.info
3
4         This program is free software: you can redistribute it and/or modify
5         it under the terms of the GNU General Public License as published by
6         the Free Software Foundation, either version 2 of the License, or
7         (at your option) any later version.
8
9         This program is distributed in the hope that it will be useful,
10         but WITHOUT ANY WARRANTY; without even the implied warranty of
11         MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12         GNU General Public License for more details.
13
14         You should have received a copy of the GNU General Public License
15         along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 */
17
18 #include "stdafx.h"
19 #include "ImageIO.h"
20
21 using namespace FM7b5;
22
23 struct PbmHeader
24 {
25         char magic[3];
26         int width;
27         int height;
28         int depth;
29
30         PbmHeader()
31                 : width(0), height(0), depth(0)
32         {
33                 magic[0] = magic[1] = magic[2] = '\0';
34         }
35 };
36
37 /* declarations for static functions */
38 static bool read_header(FILE* fp, PbmHeader* header);
39 static bool read_magic(FILE* fp, char magic[3]);
40 static bool read_digit(FILE* fp, int* val);
41 static bool read_whitespace(FILE* fp);
42 static bool skip_comment(FILE* fp);
43 static bool read_end_of_line(FILE* fp);
44
45
46 Image<std::uint8_t>
47 FM7b5::loadPGM(const std::string& filename)
48 {
49         FILE* fp(nullptr);
50
51         if (fopen_s(&fp, filename.c_str(), "rb") != 0) {
52                 throw std::runtime_error(__FUNCTION__ ": could not open a file.");
53         }
54
55         try {
56                 PbmHeader header;
57                 read_header(fp, &header);
58
59                 printf("--- header ---\n");
60                 printf("magic: %s\n", header.magic);
61                 printf("size : %dx%d\n", header.width, header.height);
62                 printf("depth: %d\n", header.depth);
63
64                 /* check if the color format is supported */
65                 if (header.magic[1] != '2' && header.magic[1] != '5') {
66                         throw std::runtime_error(__FUNCTION__ ": non support format.");
67                 }
68                 if (header.depth != 255) {
69                         throw std::runtime_error(__FUNCTION__ ": supported depth is 255 only.");
70                 }
71
72                 Image<std::uint8_t> img(header.width, header.height);
73
74                 switch (header.magic[1]) {
75                 case '2': // Gray ASCII format
76                         for (size_t h = 0; h < img.height(); ++h) {
77                                 for (size_t w = 0; w < img.width(); ++w) {
78                                         while (read_whitespace(fp));
79
80                                         int val;
81                                         if (!read_digit(fp, &val)) {
82                                                 throw std::runtime_error(__FUNCTION__ ": invalid data.");
83                                         }
84
85                                         img(w, h) = static_cast<std::uint8_t>(val);
86                                 }
87                         }
88                         break;
89
90                 case '5': // Gray binary format
91                         {
92                                 const size_t size(header.width * header.height);
93                                 size_t ret = fread(img.data(), sizeof(std::uint8_t), size, fp);
94                                 if (ret != size) {
95                                         throw std::runtime_error(__FUNCTION__ ": insufficient data.");
96                                 }
97                         }
98                         break;
99
100                 default:
101                         throw std::runtime_error(__FUNCTION__ ": unexpected format was specified.");
102                 }
103
104                 printf("successfully read (^^\n");
105
106                 return img;
107         }
108         catch (...)
109         {
110                 fclose(fp);
111                 throw;
112         }
113 }
114
115
116 bool
117 read_header(FILE* fp, PbmHeader* header)
118 {
119         if (!read_magic(fp, header->magic) || !read_whitespace(fp)) {
120                 throw std::runtime_error(__FUNCTION__ ": not a netpbm file.");
121         }
122
123         while (read_whitespace(fp)); // skip comments and white spaces
124
125         if (!read_digit(fp, &header->width) || !read_whitespace(fp)) {
126                 throw std::runtime_error(__FUNCTION__ ": invalid width.");
127         }
128
129         if (!read_digit(fp, &header->height) || !read_whitespace(fp)) {
130                 throw std::runtime_error(__FUNCTION__ ": invalid height.");
131         }
132
133         if (!read_digit(fp, &header->depth)) {
134                 throw std::runtime_error(__FUNCTION__ ": invalid depth.");
135         }
136
137         if (!read_whitespace(fp)) {
138                 throw std::runtime_error(__FUNCTION__ ": just one white space is required right after the depth value.");
139         }
140
141         return true;
142 }
143
144 bool
145 read_magic(FILE* fp, char magic[3])
146 {
147         size_t pos(0);
148
149         magic[pos++] = fgetc(fp);
150         if (magic[0] != 'P') {
151                 goto on_error;
152         }
153
154         magic[pos++] = fgetc(fp);
155         if (!isdigit(magic[1])) {
156                 goto on_error;
157         }
158         magic[2] = '\0';
159
160         return true;
161
162 on_error:
163         for (size_t i = 0; i < pos; ++i) {
164                 ungetc(magic[i], fp);
165                 magic[i] = '\0';
166         }
167         return false;
168 }
169
170 bool
171 read_digit(FILE* fp, int* val)
172 {
173         static const size_t buf_size(16);
174
175         char buf[buf_size];
176         size_t pos(0);
177         for (pos = 0; pos < buf_size; ++pos) {
178                 buf[pos] = fgetc(fp);
179                 if (!isdigit(buf[pos])) {
180                         break;
181                 }
182         }
183         if (pos == 0) {
184                 ungetc(buf[0], fp);
185                 return false;
186         }
187         if (pos == buf_size) { // too long digits
188                 for (size_t i = 0; i < buf_size; ++i) {
189                         ungetc(buf[i], fp);
190                         return false;
191                 }
192         }
193
194         // push back the next char
195         ungetc(buf[pos], fp);
196
197         // read a number
198         buf[pos] = '\0';
199         *val = atoi(buf);
200
201         return true;
202 }
203
204 bool
205 read_whitespace(FILE* fp)
206 {
207         while (skip_comment(fp));
208
209         char ch = fgetc(fp);
210
211         if (!isspace(ch)) {
212                 ungetc(ch, fp);
213                 return false;
214         }
215
216         // check CR+LF
217         if (ch == '\r') {
218                 ch = fgetc(fp);
219                 if (ch != '\n') {
220                         ungetc(ch, fp);
221                 }
222         }
223
224         return true;
225 }
226
227 bool
228 skip_comment(FILE* fp)
229 {
230         char ch = fgetc(fp);
231
232         if (ch != '#') {
233                 ungetc(ch, fp);
234                 return false;
235         }
236
237         // skip to eol
238         while (!read_end_of_line(fp)) {
239                 ch = fgetc(fp);
240         }
241
242         return true;
243 }
244
245 bool
246 read_end_of_line(FILE* fp)
247 {
248         char ch = fgetc(fp);
249
250         if (ch == '\r') {
251                 ch = fgetc(fp);
252                 if (ch != '\n') {
253                         ungetc(ch, fp);
254                 }
255         }
256         else if (ch != '\n') { // not an end_of_line
257                 ungetc(ch, fp);
258                 return false;
259         }
260
261         return true;
262 }