1 //
2 // SPDX-License-Identifier: BSD-3-Clause
3 // Copyright (c) Contributors to the OpenEXR Project.
4 //
5
6 #ifdef NDEBUG
7 # undef NDEBUG
8 #endif
9
10 #include <tmpDir.h>
11 #include <fuzzFile.h>
12
13 #include <ImfTiledRgbaFile.h>
14 #include <ImfArray.h>
15 #include <ImfThreading.h>
16 #include <ImfHeader.h>
17 #include <ImfFrameBuffer.h>
18 #include <IlmThread.h>
19 #include <Iex.h>
20 #include <iostream>
21 #include <cassert>
22 #include <stdio.h>
23
24 #include <vector>
25
26 // Handle the case when the custom namespace is not exposed
27 #include <OpenEXRConfig.h>
28 #include <ImfPartType.h>
29 #include <ImfMultiPartOutputFile.h>
30 #include <ImfTiledOutputPart.h>
31 #include <ImfMultiPartInputFile.h>
32 #include <ImfTiledInputPart.h>
33 #include <ImfChannelList.h>
34 using namespace OPENEXR_IMF_INTERNAL_NAMESPACE;
35 using namespace std;
36 using namespace IMATH_NAMESPACE;
37
38
39 namespace {
40
41 void
fillPixels(Array2D<Rgba> & pixels,int w,int h)42 fillPixels (Array2D<Rgba> &pixels, int w, int h)
43 {
44 for (int y = 0; y < h; ++y)
45 {
46 for (int x = 0; x < w; ++x)
47 {
48 Rgba &p = pixels[y][x];
49
50 p.r = 0.5 + 0.5 * sin (0.1 * x + 0.1 * y);
51 p.g = 0.5 + 0.5 * sin (0.1 * x + 0.2 * y);
52 p.b = 0.5 + 0.5 * sin (0.1 * x + 0.3 * y);
53 p.a = (p.r + p.b + p.g) / 3.0;
54 }
55 }
56 }
57
58
59 void
writeImageONE(const char fileName[],int width,int height,int xSize,int ySize,int parts,Compression comp)60 writeImageONE (const char fileName[],
61 int width, int height,
62 int xSize, int ySize,
63 int parts,
64 Compression comp)
65 {
66 cout << "levelMode 0" << ", compression " << comp << " parts " << parts << endl;
67
68 Header header (width, height);
69 header.lineOrder() = INCREASING_Y;
70 header.compression() = comp;
71
72 Array2D<Rgba> pixels (height, width);
73 fillPixels (pixels, width, height);
74
75 if(parts==1)
76 {
77 TiledRgbaOutputFile out (fileName, header, WRITE_RGBA,
78 xSize, ySize, ONE_LEVEL);
79
80 out.setFrameBuffer (&pixels[0][0], 1, width);
81 out.writeTiles (0, out.numXTiles() - 1, 0, out.numYTiles() - 1);
82 }else{
83
84 header.setTileDescription(TileDescription(xSize,ySize,ONE_LEVEL));
85
86 header.setType(TILEDIMAGE);
87
88 header.channels().insert("R",Channel(HALF));
89 header.channels().insert("G",Channel(HALF));
90 header.channels().insert("B",Channel(HALF));
91 header.channels().insert("A",Channel(HALF));
92
93
94 FrameBuffer f;
95 f.insert("R",Slice(HALF,(char *) &(pixels[0][0].r),sizeof(Rgba),width*sizeof(Rgba)));
96 f.insert("G",Slice(HALF,(char *) &(pixels[0][0].g),sizeof(Rgba),width*sizeof(Rgba)));
97 f.insert("B",Slice(HALF,(char *) &(pixels[0][0].b),sizeof(Rgba),width*sizeof(Rgba)));
98 f.insert("A",Slice(HALF,(char *) &(pixels[0][0].a),sizeof(Rgba),width*sizeof(Rgba)));
99
100
101 vector<Header> headers(parts);
102 for(int p=0;p<parts;p++)
103 {
104 headers[p]=header;
105 ostringstream o;
106 o << p;
107 headers[p].setName(o.str());
108 }
109 MultiPartOutputFile file(fileName,&headers[0],headers.size());
110 for(int p=0;p<parts;p++)
111 {
112 TiledOutputPart out(file,p);
113
114 out.setFrameBuffer(f);
115 out.writeTiles (0, out.numXTiles() - 1, 0, out.numYTiles() - 1);
116 }
117 }
118 }
119
120
121 void
readImageONE(const char fileName[])122 readImageONE (const char fileName[])
123 {
124 //
125 // Try to read the specified one-level file, which may be damaged.
126 // Reading should either succeed or throw an exception, but it
127 // should not cause a crash.
128 //
129 // attempt TiledRgbaInputFile interface
130 try
131 {
132 TiledRgbaInputFile in (fileName);
133 const Box2i &dw = in.dataWindow();
134
135 int w = dw.max.x - dw.min.x + 1;
136 int h = dw.max.y - dw.min.y + 1;
137 int dwx = dw.min.x;
138 int dwy = dw.min.y;
139
140 Array2D<Rgba> pixels (h, w);
141 in.setFrameBuffer (&pixels[-dwy][-dwx], 1, w);
142 in.readTiles (0, in.numXTiles() - 1, 0, in.numYTiles() - 1);
143 }
144 catch (...)
145 {
146 // empty
147 assert (true);
148 }
149 try
150 {
151
152 // attempt MultiPart interface
153 MultiPartInputFile in(fileName);
154 for(int p=0;p<in.parts();p++)
155 {
156 TiledInputPart inpart(in,p);
157 const Box2i &dw = inpart.header().dataWindow();
158
159 int w = dw.max.x - dw.min.x + 1;
160 int h = dw.max.y - dw.min.y + 1;
161 int dwx = dw.min.x;
162 int dwy = dw.min.y;
163
164 Array2D<Rgba> pixels (h, w);
165 FrameBuffer i;
166 i.insert("R",Slice(HALF,(char *)&(pixels[-dwy][-dwx].r),sizeof(Rgba),w*sizeof(Rgba)));
167 i.insert("G",Slice(HALF,(char *)&(pixels[-dwy][-dwx].g),sizeof(Rgba),w*sizeof(Rgba)));
168 i.insert("B",Slice(HALF,(char *)&(pixels[-dwy][-dwx].b),sizeof(Rgba),w*sizeof(Rgba)));
169 i.insert("A",Slice(HALF,(char *)&(pixels[-dwy][-dwx].a),sizeof(Rgba),w*sizeof(Rgba)));
170
171 inpart.setFrameBuffer (i);
172 inpart.readTiles (0, inpart.numXTiles() - 1, 0, inpart.numYTiles() - 1);
173 }
174 }
175 catch (...)
176 {
177 // empty
178 assert (true);
179 }
180 }
181
182
183 void
writeImageMIP(const char fileName[],int width,int height,int xSize,int ySize,int parts,Compression comp)184 writeImageMIP (const char fileName[],
185 int width, int height,
186 int xSize, int ySize,
187 int parts, ///\todo support multipart MIP files
188 Compression comp)
189 {
190 cout << "levelMode 1" << ", compression " << comp << endl;
191
192 Header header (width, height);
193 header.lineOrder() = INCREASING_Y;
194 header.compression() = comp;
195
196 Array < Array2D<Rgba> > levels;
197
198 TiledRgbaOutputFile out (fileName, header, WRITE_RGBA,
199 xSize, ySize, MIPMAP_LEVELS, ROUND_DOWN);
200
201 int numLevels = out.numLevels();
202 levels.resizeErase (numLevels);
203
204 for (int level = 0; level < out.numLevels(); ++level)
205 {
206 int levelWidth = out.levelWidth(level);
207 int levelHeight = out.levelHeight(level);
208 levels[level].resizeErase(levelHeight, levelWidth);
209 fillPixels (levels[level], levelWidth, levelHeight);
210
211 out.setFrameBuffer (&(levels[level])[0][0], 1, levelWidth);
212 out.writeTiles (0, out.numXTiles(level) - 1,
213 0, out.numYTiles(level) - 1, level);
214 }
215 }
216
217
218 void
readImageMIP(const char fileName[])219 readImageMIP (const char fileName[])
220 {
221 //
222 // Try to read the specified mipmap file, which may be damaged.
223 // Reading should either succeed or throw an exception, but it
224 // should not cause a crash.
225 //
226
227 try
228 {
229 TiledRgbaInputFile in (fileName);
230 const Box2i &dw = in.dataWindow();
231 int dwx = dw.min.x;
232 int dwy = dw.min.y;
233
234 int numLevels = in.numLevels();
235 Array < Array2D<Rgba> > levels2 (numLevels);
236
237 for (int level = 0; level < numLevels; ++level)
238 {
239 int levelWidth = in.levelWidth(level);
240 int levelHeight = in.levelHeight(level);
241 levels2[level].resizeErase(levelHeight, levelWidth);
242
243 in.setFrameBuffer (&(levels2[level])[-dwy][-dwx], 1, levelWidth);
244 in.readTiles (0, in.numXTiles(level) - 1,
245 0, in.numYTiles(level) - 1, level);
246 }
247 }
248 catch (...)
249 {
250 // empty
251 assert (true);
252 }
253 }
254
255
256 void
writeImageRIP(const char fileName[],int width,int height,int xSize,int ySize,int parts,Compression comp)257 writeImageRIP (const char fileName[],
258 int width, int height,
259 int xSize, int ySize,
260 int parts, ///\todo support multipart RIP files
261 Compression comp)
262 {
263 cout << "levelMode 2" << ", compression " << comp << endl;
264
265 Header header (width, height);
266 header.lineOrder() = INCREASING_Y;
267 header.compression() = comp;
268
269 Array2D < Array2D<Rgba> > levels;
270
271 TiledRgbaOutputFile out (fileName, header, WRITE_RGBA,
272 xSize, ySize, RIPMAP_LEVELS, ROUND_UP);
273
274 levels.resizeErase (out.numYLevels(), out.numXLevels());
275
276 for (int ylevel = 0; ylevel < out.numYLevels(); ++ylevel)
277 {
278 for (int xlevel = 0; xlevel < out.numXLevels(); ++xlevel)
279 {
280 int levelWidth = out.levelWidth(xlevel);
281 int levelHeight = out.levelHeight(ylevel);
282 levels[ylevel][xlevel].resizeErase(levelHeight, levelWidth);
283 fillPixels (levels[ylevel][xlevel], levelWidth, levelHeight);
284
285 out.setFrameBuffer (&(levels[ylevel][xlevel])[0][0], 1,
286 levelWidth);
287 out.writeTiles (0, out.numXTiles(xlevel) - 1,
288 0, out.numYTiles(ylevel) - 1, xlevel, ylevel);
289 }
290 }
291 }
292
293
294 void
readImageRIP(const char fileName[])295 readImageRIP (const char fileName[])
296 {
297 //
298 // Try to read the specified ripmap file, which may be damaged.
299 // Reading should either succeed or throw an exception, but it
300 // should not cause a crash.
301 //
302
303 try
304 {
305 TiledRgbaInputFile in (fileName);
306 const Box2i &dw = in.dataWindow();
307 int dwx = dw.min.x;
308 int dwy = dw.min.y;
309
310 int numXLevels = in.numXLevels();
311 int numYLevels = in.numYLevels();
312 Array2D < Array2D<Rgba> > levels2 (numYLevels, numXLevels);
313
314 for (int ylevel = 0; ylevel < numYLevels; ++ylevel)
315 {
316 for (int xlevel = 0; xlevel < numXLevels; ++xlevel)
317 {
318 int levelWidth = in.levelWidth(xlevel);
319 int levelHeight = in.levelHeight(ylevel);
320 levels2[ylevel][xlevel].resizeErase(levelHeight, levelWidth);
321 in.setFrameBuffer (&(levels2[ylevel][xlevel])[-dwy][-dwx], 1,
322 levelWidth);
323
324 in.readTiles (0, in.numXTiles(xlevel) - 1,
325 0, in.numYTiles(ylevel) - 1, xlevel, ylevel);
326 }
327 }
328 }
329 catch (...)
330 {
331 // empty
332 assert (true);
333 }
334 }
335
336
337 void
fuzzTiles(int numThreads,Rand48 & random)338 fuzzTiles (int numThreads, Rand48 &random)
339 {
340 if (ILMTHREAD_NAMESPACE::supportsThreads())
341 {
342 setGlobalThreadCount (numThreads);
343 cout << "\nnumber of threads: " << globalThreadCount() << endl;
344 }
345
346 Header::setMaxImageSize (10000, 10000);
347 Header::setMaxTileSize (10000, 10000);
348
349 const int W = 217;
350 const int H = 197;
351 const int TW = 64;
352 const int TH = 64;
353
354 const char *goodFile = IMF_TMP_DIR "imf_test_tile_file_fuzz_good.exr";
355 const char *brokenFile = IMF_TMP_DIR "imf_test_tile_file_fuzz_broken.exr";
356
357 for (int parts = 1 ; parts < 3 ; parts++)
358 {
359 for (int comp = 0; comp < NUM_COMPRESSION_METHODS; ++comp)
360 {
361 writeImageONE (goodFile, W, H, TW, TH, parts , Compression (comp));
362 fuzzFile (goodFile, brokenFile, readImageONE, 5000, 3000, random);
363
364 if(parts==1)
365 {
366 writeImageMIP (goodFile, W, H, TW, TH, parts , Compression (comp));
367 fuzzFile (goodFile, brokenFile, readImageMIP, 5000, 3000, random);
368
369 writeImageRIP (goodFile, W, H, TW, TH, parts , Compression (comp));
370 fuzzFile (goodFile, brokenFile, readImageRIP, 5000, 3000, random);
371 }
372 }
373 }
374 remove (goodFile);
375 remove (brokenFile);
376 }
377
378 } // namespace
379
380
381 void
testFuzzTiles(const char * file)382 testFuzzTiles (const char* file)
383 {
384 try
385 {
386 if(file)
387 {
388 readImageONE(file);
389 readImageMIP(file);
390 readImageRIP(file);
391 }
392 else
393 {
394 cout << "Testing tile-based files "
395 "with randomly inserted errors" << endl;
396
397 Rand48 random (5);
398
399 fuzzTiles (0, random);
400
401 if (ILMTHREAD_NAMESPACE::supportsThreads())
402 fuzzTiles (2, random);
403
404 cout << "ok\n" << endl;
405 }
406 }
407 catch (const std::exception &e)
408 {
409 cerr << "ERROR -- caught exception: " << e.what() << endl;
410 assert (false);
411 }
412 }
413