VideoTools
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Pages
gvmask.cpp
Go to the documentation of this file.
1 //
2 // Copyright (C) 2017 Graeme Walker
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 3 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 // gvmask.cpp
19 //
20 
21 #include "gdef.h"
22 #include "gvmask.h"
23 #include "groot.h"
24 #include "gfile.h"
25 #include "grpnm.h"
26 #include "grscaler.h"
27 #include "gstr.h"
28 #include "glog.h"
29 #include "gassert.h"
30 #include <algorithm> // std::min/max
31 #include <utility>
32 #include <exception>
33 #include <iostream>
34 #include <fstream>
35 #include <stdexcept>
36 
37 Gv::Mask::Mask( int dx , int dy , const std::string & path , bool create ) :
38  m_dx(dx) ,
39  m_dy(dy) ,
40  m_path(path) ,
41  m_empty(true) ,
42  m_map(dx*dy) ,
43  m_map_current(dx*dy) ,
44  m_down(false) ,
45  m_down_x(0) ,
46  m_down_y(0) ,
47  m_down_shift(false) ,
48  m_file_time(0)
49 {
50  G_ASSERT( m_dx > 0 && m_dy > 0 ) ;
51  if( m_path != G::Path() )
52  {
53  // create the file if it doesnt exist
54  findOrCreate( create ) ;
55 
56  // open the file
57  std::ifstream file ;
58  open( file ) ;
59 
60  // read the pnm data
61  Gr::ImageData image_data ;
62  Gr::PnmInfo info = read( file , image_data ) ;
63 
64  // subsample or supersample to convert from file_size to m_dx/dy
65  sample( image_data , info ) ;
66  }
67 }
68 
69 void Gv::Mask::sample( const Gr::ImageData & image_data , const Gr::PnmInfo & info )
70 {
71  for( Gr::Scaler y_scaler(m_dy,info.dy()) ; !!y_scaler ; ++y_scaler )
72  {
73  for( Gr::Scaler x_scaler(m_dx,info.dx()) ; !!x_scaler ; ++x_scaler )
74  {
75  // black pixels in the mask are masked out, but note that black
76  // pixels are "1" in a pnm file, albeit parsed to zero rgb values
77  bool masked = 0 == image_data.r( x_scaler.second() , y_scaler.second() ) ;
78  if( masked ) m_empty = false ;
79  m_map[ y_scaler.first() * m_dx + x_scaler.first() ] = masked ;
80  }
81  }
82 }
83 
84 void Gv::Mask::findOrCreate( bool create )
85 {
86  bool exists = false ;
87  {
88  G::Root claim_root ;
89  exists = G::File::exists( m_path ) ;
90  }
91  G_DEBUG( "Gv::Mask::ctor: exists=" << exists ) ;
92  if( !exists && !create )
93  throw std::runtime_error( "cannot open mask file [" + m_path.str() + "]" ) ;
94  else if( !exists )
95  write( m_path ) ;
96 }
97 
98 void Gv::Mask::open( std::ifstream & file )
99 {
100  G::Root claim_root ;
101  m_file_time = G::File::time( m_path , G::File::NoThrow() ) ;
102  file.open( m_path.str().c_str() ) ;
103 }
104 
105 Gr::PnmInfo Gv::Mask::read( std::ifstream & file , Gr::ImageData & image_data )
106 {
107  Gr::PnmInfo info ;
108  try
109  {
110  Gr::PnmReader reader ;
111  reader.decode( image_data , file ) ;
112  info = reader.info() ;
113  G_DEBUG( "Gv::Mask::ctor: P" << info.pn() ) ;
114  }
115  catch( std::exception & )
116  {
117  G_ASSERT( !info.valid() ) ;
118  }
119  if( !info.valid() || ( info.pn() != 1 && info.pn() != 4 ) )
120  throw std::runtime_error( "invalid mask file format: [" + m_path.str() + "] is not a pgm bitmap" ) ;
121  return info ;
122 }
123 
124 bool Gv::Mask::empty() const
125 {
126  return m_empty ;
127 }
128 
130 {
131  return m_file_time ;
132 }
133 
134 void Gv::Mask::write( const G::Path & path ) const
135 {
136  G_DEBUG( "Gv::Mask::write: writing [" << path << "]" ) ;
137  std::ofstream f ;
138  {
139  G::Root claim_root ;
140  f.open( path.str().c_str() , std::ios_base::trunc ) ;
141  }
142  if( !f.good() )
143  throw std::runtime_error( "cannot write mask file [" + path.str() + "]" ) ;
144 
145  f << "P1\n" ;
146  f << m_dx << " " << m_dy << "\n" ;
147  for( int y = 0 ; y < m_dy ; y++ )
148  {
149  const char * sep = "" ;
150  for( int x = 0 ; x < m_dx ; x++ , sep = " " )
151  {
152  f << sep << (m_map[y*m_dx+x]?"1":"0") ; // masked-out = black = "1"
153  }
154  f << "\n" ;
155  }
156  f << "\n" << std::flush ;
157  if( !f.good() )
158  throw std::runtime_error( "cannot write mask file [" + path.str() + "]" ) ;
159 }
160 
161 int Gv::Mask::clip_x( int x ) const
162 {
163  return std::min( std::max(0,x) , m_dx-1 ) ;
164 }
165 
166 int Gv::Mask::clip_y( int y ) const
167 {
168  return std::min( std::max(0,y) , m_dy-1 ) ;
169 }
170 
171 void Gv::Mask::down( int x , int y , bool shift , bool control )
172 {
173  m_down = true ;
174  m_down_x = m_move_x = clip_x( x ) ;
175  m_down_y = m_move_y = clip_y( y ) ;
176  m_down_shift = shift ;
177 }
178 
179 void Gv::Mask::move( int x , int y )
180 {
181  if( m_down )
182  {
183  fill( m_map_current , m_down_x , m_down_y , m_move_x , m_move_y , false ) ;
184  m_move_x = clip_x(x) ;
185  m_move_y = clip_y(y) ;
186  fill( m_map_current , m_down_x , m_down_y , m_move_x , m_move_y , true ) ;
187  }
188 }
189 
190 bool Gv::Mask::fill( std::vector<bool> & map , int x0 , int y0 , int x1 , int y1 , bool b )
191 {
192  bool changed = false ;
193  for( int x = std::min(x0,x1) ; x < std::max(x0,x1) ; x++ )
194  {
195  for( int y = std::min(y0,y1) ; y < std::max(y0,y1) ; y++ )
196  {
197  size_t offset = y*m_dx+x ;
198  if( !changed && (map[offset]!= b) ) changed = true ;
199  map[offset] = b ;
200  }
201  }
202  return changed ;
203 }
204 
205 void Gv::Mask::up( int up_x , int up_y , bool shift , bool control )
206 {
207  m_down = false ;
208  m_map_current.assign( m_map_current.size() , false ) ;
209  if( fill( m_map , m_down_x , m_down_y , clip_x(up_x) , clip_y(up_y) , !m_down_shift ) && !m_down_shift )
210  m_empty = false ;
211 }
212 
214 {
215  if( m_path == G::Path() )
216  {
217  return false ;
218  }
219  else
220  {
221  G::EpochTime new_time = G::File::time( m_path , G::File::NoThrow() ) ;
222  if( m_file_time == G::EpochTime(0) || new_time != m_file_time )
223  {
224  Gr::ImageData image_data ;
225  Gr::PnmInfo info ;
226  try
227  {
228  std::ifstream file ;
229  {
230  G::Root claim_root ;
231  file.open( m_path.str().c_str() ) ;
232  }
233  info = read( file , image_data ) ;
234  }
235  catch( std::exception & e )
236  {
237  return false ;
238  }
239  sample( image_data , info ) ;
240  m_file_time = new_time ;
241  return true ;
242  }
243  else
244  {
245  return false ;
246  }
247  }
248 }
249 
250 /// \file gvmask.cpp
std::string str() const
Returns the path string.
Definition: gpath.cpp:290
A subsecond-resolution timestamp based on a time_t.
Definition: gdatetime.h:39
bool valid() const
Returns true if successfully constructed.
Definition: grpnm.h:156
int dx() const
Returns the image width.
Definition: grpnm.h:150
A holder for image data, having eight bits per sample and one or three channels.
Definition: grimagedata.h:46
unsigned char r(int x, int y) const
Returns the R-value for a point.
Definition: grimagedata.h:390
int pn() const
Returns the p-number.
Definition: grpnm.h:153
A class which acquires the process's special privileges on construction and releases them on destruct...
Definition: groot.h:49
void up(int x, int y, bool shift, bool control)
Called on mouse-up. Commits any down()/move() edits.
Definition: gvmask.cpp:205
A class that allows for iterating over one integer range while accessing values from another...
Definition: grscaler.h:89
void move(int x, int y)
Called on mouse-move. Modifies the current edit.
Definition: gvmask.cpp:179
bool empty() const
Returns true if empty.
Definition: gvmask.cpp:124
G::EpochTime time() const
Returns the timestamp on the mask file at construction, not affected by any calls to write()...
Definition: gvmask.cpp:129
bool update()
Updates the mask from the file. Returns true if updated.
Definition: gvmask.cpp:213
int dy() const
Returns the image height.
Definition: grpnm.h:151
void decode(ImageData &out, const G::Path &in)
Decodes a pnm file into an image. Throws on error.
Definition: grpnm.cpp:383
void write(const G::Path &filename) const
Writes to file.
Definition: gvmask.cpp:134
static bool exists(const Path &file)
Returns true if the file (directory, device etc.) exists.
Definition: gfile.cpp:132
Mask(int dx, int dy, const std::string &file=std::string(), bool create=false)
Constructor, optionally reading the mask from an existing file.
Definition: gvmask.cpp:37
An overload discriminator class for File methods.
Definition: gfile.h:53
void down(int x, int y, bool shift, bool control)
Called on mouse-down. Starts an edit.
Definition: gvmask.cpp:171
A structure holding portable-anymap metadata.
Definition: grpnm.h:42
static EpochTime time(const Path &file)
Returns the file's timestamp.
Definition: gfile_unix.cpp:101
A static interface for reading portable-anymap (pnm) files.
Definition: grpnm.h:110
const PnmInfo & info() const
Returns the pnm header info, ignoring scaling.
Definition: grpnm.h:158
A Path object represents a file system path.
Definition: gpath.h:72