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