1+ #include < brunsli/encode.h>
2+ #include < brunsli/decode.h>
3+ #include < string>
4+ #include < iostream>
5+ #include < vector>
6+ #include < algorithm>
7+ #include < cassert>
8+ #include < cstdio>
9+ #include < sys/stat.h>
10+ #include < cstdlib>
11+ #include < fcntl.h>
12+ #include < sys/types.h>
13+
14+ #if defined(_WIN32)
15+ #if !defined(_WIN64)
16+ #error "Need 64bit support"
17+ #endif
18+ #include < Windows.h>
19+ #include < io.h>
20+
21+ #define FSEEK _fseeki64
22+ #define FTELL _ftelli64
23+
24+ // Windows is always little endian, supply functions to swap bytes
25+ // These are defined in <cstdlib>
26+ #define htobe64 _byteswap_uint64
27+ #define be64toh _byteswap_uint64
28+
29+ #else // Linux
30+
31+ #include < unistd.h>
32+ #include < endian.h>
33+ // Check that we can seek large files
34+ static_assert (sizeof (off_t ) == 8 );
35+ #define FSEEK fseeko
36+ #define FTELL ftello
37+ #endif
38+
39+ using namespace std ;
40+
41+ int Usage (const string &s) {
42+ std::cerr << s << endl << endl
43+ << " Synopsis: brn [OPTIONS] <source-file>\n "
44+ << " \t -r\t Reverse, convert BRUNSLI input to JFIF\n "
45+ << " \t -b\t Bundle (esri v2) input, default is MRF\n "
46+ << " \t -s\t Single image, input is a JFIF or BRUNSLI (with -r)\n " ;
47+ return 1 ;
48+ }
49+
50+ static size_t out_fun (vector<uint8_t > *output, const uint8_t *data, size_t size) {
51+ copy (data, data+size, back_inserter (*output));
52+ return size;
53+ }
54+
55+ // Big Endian native
56+ struct tinfo {
57+ uint64_t offset;
58+ uint64_t size;
59+ void toh () {
60+ offset = be64toh (offset);
61+ size = be64toh (size);
62+ }
63+ void tonative () {
64+ offset = htobe64 (offset);
65+ size = htobe64 (size);
66+ }
67+ };
68+
69+ // Single file, either BRUNSLI or JFIF
70+ int single_to_brn (const string &inname, bool reverse = false ) {
71+ auto outname = inname + (reverse ? " .jfif" : " .brn" );
72+ auto fin = fopen (inname.c_str (), " rb" );
73+ if (!fin) return Usage (" Can't open input file" );
74+ FSEEK (fin, 0 , SEEK_END);
75+ auto insize = ftell (fin);
76+ rewind (fin);
77+ // Check a max size of 128MB
78+ if (insize > (1ull << 27 )) return Usage (" Input file too large, limit is 128MB" );
79+ vector<uint8_t > input (insize);
80+ fread (input.data (), insize, 1 , fin);
81+ fclose (fin);
82+ auto fout = fopen (outname.c_str (), " wb" );
83+ if (!fout) return Usage (" Can't open output file" );
84+ // Convert
85+ vector<uint8_t > tilebuf;
86+ int result = reverse ?
87+ DecodeBrunsli (insize, input.data (), &tilebuf, (DecodeBrunsliSink)out_fun)
88+ : EncodeBrunsli (insize, input.data (), &tilebuf, (DecodeBrunsliSink)out_fun);
89+ if (!result) return Usage (reverse ? " Error decoding BRUNSLI" : " Error encoding BRUNSLI" );
90+ fwrite (tilebuf.data (), tilebuf.size (), 1 , fout);
91+ fclose (fout);
92+ return 0 ;
93+ }
94+
95+ // From MRF, separate files, inname is the data file
96+ int mrf_to_brn (const string &inname, const string &outname, bool reverse = false ) {
97+ // Assume three letter data file extension
98+ if (' .' != inname[inname.size () - 4 ])
99+ return Usage (" Expect mrf data file with three letter file name extension" );
100+
101+ struct stat statb;
102+ if (stat (inname.c_str (), &statb))
103+ return Usage (" Can't stat input file" );
104+ auto insize = statb.st_size ;
105+
106+ // Indes should be same file with extension changed
107+ string inidxname (inname);
108+ inidxname.resize (inidxname.size () - 3 );
109+ inidxname += " idx" ;
110+ // cout << "Opening " << inname << " and " << inidxname << endl;
111+ auto finidx = fopen (inidxname.c_str (), " rb" );
112+ auto fin = fopen (inname.c_str (), " rb" );
113+ if (!finidx || !fin)
114+ return Usage (" Can't open input data or index file" );
115+
116+ string outidxname (outname.substr (0 , outname.size () - 4 ) + " .idx" );
117+ // cout << "Opening " << outname << " and " << outidxname << endl;
118+ auto fout = fopen (outname.c_str (), " wb" );
119+ auto foutidx = fopen (outidxname.c_str (), " wb" );
120+ tinfo tile;
121+ vector<uint8_t > input;
122+ vector<uint8_t > tilebuf;
123+ uint64_t ooff = 0 ;
124+
125+ // Stats, saving ratio
126+ double min_rat = 1 ;
127+ double max_rat = -100 ;
128+ while (fread (&tile, sizeof (tile), 1 , finidx)) {
129+ if (0 == tile.size ) continue ;
130+ tile.toh ();
131+ FSEEK (fin, tile.offset , SEEK_SET);
132+ input.resize (tile.size );
133+ if (!fread (input.data (), tile.size , 1 , fin)) {
134+ std::cerr << " Location " << hex << tile.offset << " size " << tile.size << dec << endl;
135+ return Usage (" Failed to read input tile" );
136+ }
137+ tilebuf.clear ();
138+
139+ int result = reverse ?
140+ DecodeBrunsli (tile.size , input.data (), &tilebuf, (DecodeBrunsliSink)out_fun)
141+ : EncodeBrunsli (tile.size , input.data (), &tilebuf, (DecodeBrunsliSink)out_fun);
142+ if (!result) {
143+ std::cerr << " Location " << hex << tile.offset << " size " << tile.size << dec << endl;
144+ return Usage (reverse ? " Error decoding BRUNSLI" : " Error encoding BRUNSLI" );
145+ }
146+
147+ double rat = 1 - double (tilebuf.size ()) / tile.size ;
148+ min_rat = min (rat, min_rat);
149+ max_rat = max (rat, max_rat);
150+
151+ // Prepare the output tinfo
152+ tile.offset = ooff;
153+ tile.size = tilebuf.size ();
154+ ooff += tile.size ;
155+ if (!fwrite (tilebuf.data (), tilebuf.size (), 1 , fout))
156+ return Usage (" Error writing data" );
157+ tile.tonative ();
158+ fwrite (&tile, sizeof (tile), 1 , foutidx);
159+ }
160+ fclose (fin);
161+ fclose (finidx);
162+ fclose (fout);
163+ fclose (foutidx);
164+
165+ std::cerr << " Used to be " << insize << " now " << ooff << " , saved " << (1 - double (ooff)/insize) * 100 << " %\n " ;
166+ std::cerr << " Individual tile saving between " << min_rat * 100 << " % and " << max_rat * 100 << " %\n " ;
167+
168+ return 0 ;
169+ }
170+
171+ struct bundle_index {
172+ uint64_t offset : 40 ;
173+ uint64_t size : 24 ;
174+ // bool operator<(const bundle_index& other) {
175+ // return offset < other.offset;
176+ // }
177+ };
178+
179+ static_assert (sizeof (bundle_index) == 8 );
180+
181+ constexpr size_t BSZ = 128 ;
182+ constexpr size_t BSZ2 = BSZ * BSZ;
183+ constexpr size_t HDRSZ = 64 ;
184+ constexpr size_t IDXSZ = BSZ2 * sizeof (bundle_index);
185+
186+ int bundle_to_brn (const string &inname, const string &outname, bool reverse = false )
187+ {
188+ struct stat statb;
189+ if (stat (inname.c_str (), &statb))
190+ return Usage (" Can't stat input file" );
191+ auto insize = statb.st_size ;
192+ if (insize < (HDRSZ + IDXSZ))
193+ return Usage (" Input file too small, can't be a bundle" );
194+
195+ auto fin = fopen (inname.c_str (), " rb" );
196+ if (!fin) return Usage (" Can't open input file" );
197+
198+ // TODO: define header as struct
199+ char header[64 ];
200+ if (1 != fread (header, sizeof (header), 1 , fin))
201+ return Usage (" Can't read from input bundle file" );
202+
203+ // Read compact bundle index
204+ vector<bundle_index> idx (BSZ2);
205+ if (BSZ2 != fread (idx.data (), sizeof (bundle_index), BSZ2, fin))
206+ return Usage (" Can't read bundle index" );
207+
208+ // TODO: Swap after reading if not little endian
209+
210+ // Check for out of bounds
211+ for (auto &v : idx)
212+ if ((v.offset + v.size ) > insize)
213+ return Usage (" Corrupt bundle, index points past the end of the file" );
214+
215+ // Prepare output
216+ FILE *fout = fopen (outname.c_str (), " wb" );
217+ if (!fout)
218+ return Usage (" Can't open output file" );
219+ // Write the input header + index, to have the right placement
220+ size_t ooff = HDRSZ + IDXSZ;
221+ FSEEK (fout, ooff, SEEK_SET);
222+
223+ // Convert, writing output as we go, reusing the index
224+ vector<uint8_t > input;
225+ vector<uint8_t > tilebuf;
226+ size_t maxsz = 0 ;
227+ // Stats, saving ratio
228+ double min_rat = 1 ;
229+ double max_rat = -100 ;
230+
231+ for (auto &tile : idx) {
232+ if (0 == tile.size ) continue ;
233+ tilebuf.clear ();
234+
235+ FSEEK (fin, tile.offset , SEEK_SET);
236+ input.resize (tile.size );
237+ if (!fread (input.data (), tile.size , 1 , fin)) {
238+ std::cerr << " Location " << hex << tile.offset << " size " << tile.size << dec << endl;
239+ return Usage (" Failed to read input tile" );
240+ }
241+
242+ int result = reverse ?
243+ DecodeBrunsli (tile.size , input.data (), &tilebuf, (DecodeBrunsliSink)out_fun)
244+ : EncodeBrunsli (tile.size , input.data (), &tilebuf, (DecodeBrunsliSink)out_fun);
245+ if (!result) {
246+ cerr << " Location " << hex << tile.offset << " size " << tile.size << endl;
247+ return Usage (reverse ? " Error decoding BRUNSLI" : " Error encoding BRUNSLI" );
248+ }
249+
250+ // Check that the output tile size fits on 24bits
251+ uint32_t tilesz = uint32_t (tilebuf.size ());
252+ if (tilesz >= (1 << 24 ) || static_cast <size_t >(tilesz) != tilebuf.size ()) {
253+ cerr << " Location " << hex << tile.offset << " size " << tile.size <<
254+ " converted to " << tilebuf.size () << endl;
255+ return Usage (" Output tile size too big" );
256+ }
257+
258+ double rat = 1 - double (tilesz) / tile.size ;
259+ min_rat = min (rat, min_rat);
260+ max_rat = max (rat, max_rat);
261+ tile.offset = ooff + 4 ;
262+ tile.size = tilebuf.size ();
263+ ooff += tile.size + 4 ;
264+
265+ // Looks good, write the output tile, prefixed by size
266+ uint32_t size_prefix = tile.size ;
267+ if (!fwrite (&size_prefix, sizeof (size_prefix), 1 , fout) ||
268+ !fwrite (tilebuf.data (), tilebuf.size (), 1 , fout))
269+ return Usage (" Failed to write output data" );
270+
271+ // Collect stats
272+ maxsz = max (maxsz, static_cast <size_t >(tile.size ));
273+ }
274+ // Done with the input
275+ fclose (fin);
276+
277+ // Update header and index at the start of the output bundle
278+ rewind (fout);
279+ // Modify maxtilesize and file size only
280+ memcpy (header + 8 , &maxsz, 4 ); // Assume little endian
281+ memcpy (header + 24 , &ooff, sizeof (ooff));
282+ fwrite (header, sizeof (header), 1 , fout);
283+ fwrite (idx.data (), IDXSZ, 1 , fout);
284+ fclose (fout);
285+
286+ std::cerr << " Used to be " << insize << " now " << ooff
287+ << " , saved " << (1 - double (ooff)/insize) * 100 << " %\n " ;
288+ std::cerr << " Individual tile saving between " << min_rat * 100
289+ << " % and " << max_rat * 100 << " %\n " ;
290+ std::cerr << " Maxtile " << maxsz << endl;
291+ return 0 ;
292+ }
293+
294+ int main (int argc, char **argv)
295+ {
296+ bool reverse = false ; // defaults to JPEG -> BRUNSLI
297+ bool bundle = false ; // defaults to MRF
298+ bool single = false ; // single jpeg
299+ string input_name;
300+ while (--argc) {
301+ string this_arg (argv[argc]);
302+ if (this_arg == " -r" ) {
303+ reverse = true ;
304+ } else if (this_arg == " -b" ) {
305+ bundle = true ;
306+ } else if (this_arg == " -s" ) {
307+ single = true ;
308+ } else {
309+ input_name = this_arg;
310+ }
311+ }
312+
313+ if (input_name.empty ())
314+ return Usage (" Needs input file name" );
315+
316+ if (single)
317+ return single_to_brn (input_name, reverse);
318+ if (bundle)
319+ return bundle_to_brn (input_name, input_name + " .brn" , reverse);
320+ return mrf_to_brn (input_name, input_name + " .brn" , reverse);
321+ }
0 commit comments