PNGArena.cxx
Go to the documentation of this file.
2 
3 #include "TString.h"
4 
5 #include <iostream>
6 
7 #include <png.h>
8 #include <thread>
9 #include <zlib.h> // for Z_RLE
10 
11 namespace evd
12 {
13  // --------------------------------------------------------------------------
14  PNGArena::PNGArena(const std::string& n) : name(n), nviews(0)
15  {
16  }
17 
18  // --------------------------------------------------------------------------
20  {
21  const int nfitx = kArenaSize/kBlockSize;
22  const int nfity = kArenaSize/kBlockSize;
23 
24  int ix = nviews%nfitx;
25  int iy = nviews/nfitx;
26 
27  if(data.empty() || iy >= nfity){
28  ix = 0;
29  iy = 0;
30  nviews = 0;
31  data.push_back(std::make_unique<std::array<png_byte, kTotBytes>>());
32  data.back()->fill(0);
33  fHasMIP.push_back(false);
34  fMIPLocks.push_back(new std::mutex);
35  }
36 
37  ++nviews;
38 
39  return &(*data.back())[(iy*kArenaSize + ix)*kBlockSize*4];
40  }
41 
42  // --------------------------------------------------------------------------
43  void PNGArena::FillMipMaps(int d)// const
44  {
45  png_byte* src = data[d]->data();
46  png_byte* dest = data[d]->data() + kArenaSize*kArenaSize*4;
47 
48  for(int newdim = kArenaSize/2; newdim >= 1; newdim /= 2){
49  const int olddim = newdim*2;
50 
51  // The alpha channel is set to twice the average of the source
52  // pixels. The intuition is that a linear feature should remain equally
53  // as bright, but would be expected to only hit 2 of the 4 source
54  // pixels. The colour channels are averaged, weighted by the alpha
55  // values.
56  for(int y = 0; y < newdim; ++y){
57  for(int x = 0; x < newdim; ++x){
58  int totc[3] = {0,};
59  int tota = 0;
60  for(int dy = 0; dy <= 1; ++dy){
61  for(int dx = 0; dx <= 1; ++dx){
62  const int i = (y*2+dy)*olddim + (x*2+dx);
63 
64  const png_byte va = src[i*4+3]; // alpha value
65  tota += va;
66 
67  for(int c = 0; c < 3; ++c){
68  const png_byte vc = src[i*4+c]; // colour value
69  totc[c] += vc * va;
70  } // end for c
71  } // end for dx
72  } // end for dy
73 
74  const int i = y*newdim+x;
75  for(int c = 0; c < 3; ++c) dest[i*4+c] = tota > 0 ? std::min(totc[c]/tota, 255) : 0;
76  dest[i*4+3] = std::min(tota/2, 255);
77  } // end for x
78  } // end for y
79 
80  src = dest;
81  dest += 4*newdim*newdim;
82  } // end for newdim
83  }
84 
85  // This is essentially what the libpng default version would be anyway. But
86  // by writing it ourselves we can just ignore failure (probably the browser
87  // hung up), rather than getting entangled in libpng's error handling.
88  void webevd_png_write_fn(png_struct_def* png_ptr,
89  png_byte* buffer,
90  long unsigned int nbytes)
91  {
92  FILE* fout = (FILE*)png_get_io_ptr(png_ptr);
93  const size_t ret = fwrite(buffer, 1, nbytes, fout);
94 
95  if(ret != nbytes){
96  std::cout << "Error writing " << nbytes << " bytes of png -- returned " << ret << std::endl;
97  perror(0);
98  std::cout << std::endl;
99  }
100  }
101 
102  void webevd_png_flush_fn(png_struct_def* png_ptr)
103  {
104  // Nothing to do here, but libpng requires we provide this
105  }
106 
107  // --------------------------------------------------------------------------
108  void PNGArena::WritePNGBytes(FILE* fout, int imgIdx, int dim)
109  {
110  if(imgIdx >= int(data.size())){
111  std::cout << "Bad index " << imgIdx << std::endl;
112  return; // TODO return 404 instead
113  }
114 
115  if(dim != kArenaSize){ // might need MIPMaps generated
116  std::lock_guard<std::mutex> guard(*fMIPLocks[imgIdx]);
117  if(!fHasMIP[imgIdx]){
118  FillMipMaps(imgIdx);
119  fHasMIP[imgIdx] = true;
120  }
121  }
122 
123  // Figure out offset
124  png_byte* src = &(*data[imgIdx])[MipMapOffset(dim, kArenaSize)];
125 
126  if(src + 4*dim*dim - 1 > &(*data[imgIdx]).back()){ // would run off end of data
127  std::cout << "Bad mipmap size " << dim << std::endl;
128  return; // TODO return 404 instead
129  }
130 
131  png_struct_def* png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
132 
133  auto info_ptr = png_create_info_struct(png_ptr);
134 
135  // I'm trying to optimize the compression speed, without blowing the file
136  // size up. It's far from clear these are the best parameters, but they do
137  // seem to be an improvement on the defaults.
138  png_set_compression_level(png_ptr, 1);
139  png_set_compression_strategy(png_ptr, Z_RLE);
140 
141  png_set_IHDR(png_ptr, info_ptr, dim, dim,
142  8/*bit_depth*/, PNG_COLOR_TYPE_RGBA, PNG_INTERLACE_NONE,
143  PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
144 
145  std::vector<png_byte*> pdatas(dim);
146  for(int i = 0; i < dim; ++i) pdatas[i] = src + i*dim*4;
147 
148  png_set_rows(png_ptr, info_ptr, pdatas.data());
149 
150  // This works fine until libpng throws a fatal error when the browser hangs
151  // up
152  // png_init_io(png_ptr, fout);
153 
154  // So set our own write function that does the same thing but ignores
155  // errors
156  png_set_write_fn(png_ptr, fout, webevd_png_write_fn, webevd_png_flush_fn);
157 
158  png_write_png(png_ptr, info_ptr, PNG_TRANSFORM_IDENTITY, NULL);
159 
160  png_destroy_write_struct(&png_ptr, &info_ptr);
161  }
162 
163  // --------------------------------------------------------------------------
165  {
166  }
167 
168  // --------------------------------------------------------------------------
170  {
171  for(int blockSize = 1; blockSize <= PNGArena::kArenaSize; blockSize *= 2){
172  long nfilled = 0;
173  for(unsigned int d = 0; d < bytes.data.size(); ++d){
174  for(int iy = 0; iy < PNGArena::kArenaSize/blockSize; ++iy){
175  for(int ix = 0; ix < PNGArena::kArenaSize/blockSize; ++ix){
176  bool filled = false;
177  for(int y = 0; y < blockSize && !filled; ++y){
178  for(int x = 0; x < blockSize; ++x){
179  if(bytes(d, ix*blockSize+x, iy*blockSize+y, 3) > 0){
180  filled = true;
181  break;
182  }
183  }
184  } // end for y
185  if(filled) ++nfilled;
186  }
187  }
188  }
189 
190  std::cout << "With block size = " << blockSize << " " << double(nfilled)/((PNGArena::kArenaSize*PNGArena::kArenaSize)/(blockSize*blockSize)*bytes.data.size()) << " of the blocks are filled" << std::endl;
191  }
192  }
193 
194 } //namespace
static QCString name
Definition: declinfo.cpp:673
void webevd_png_write_fn(png_struct_def *png_ptr, png_byte *buffer, long unsigned int nbytes)
Definition: PNGArena.cxx:88
unsigned char png_byte
Definition: PNGArena.h:10
PNGView(PNGArena &a)
Definition: PNGArena.cxx:164
void webevd_png_flush_fn(png_struct_def *png_ptr)
Definition: PNGArena.cxx:102
std::vector< std::unique_ptr< std::array< png_byte, kTotBytes > > > data
Definition: PNGArena.h:59
std::string string
Definition: nybbler.cc:12
static constexpr int MipMapOffset(int dim, int maxdim)
Definition: PNGArena.h:16
void FillMipMaps(int d)
Definition: PNGArena.cxx:43
LArSoft includes.
Definition: InfoTransfer.h:33
std::void_t< T > n
const double a
void AnalyzeArena(const PNGArena &bytes)
Definition: PNGArena.cxx:169
png_byte * NewBlock()
Definition: PNGArena.cxx:19
std::vector< bool > fHasMIP
Definition: PNGArena.h:65
PNGArena(const std::string &name)
Definition: PNGArena.cxx:14
T min(sqlite3 *const db, std::string const &table_name, std::string const &column_name)
Definition: statistics.h:55
list x
Definition: train.py:276
void WritePNGBytes(FILE *fout, int imgIdx, int dim)
Definition: PNGArena.cxx:108
byte bytes
Alias for common language habits.
Definition: datasize.h:101
std::vector< std::mutex * > fMIPLocks
Definition: PNGArena.h:64
QTextStream & endl(QTextStream &s)