VideoTools
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Pages
grimagedata.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 // grimagedata.cpp
19 //
20 
21 #include "gdef.h"
22 #include "grdef.h"
23 #include "grimagedata.h"
24 #include "grcolourspace.h"
25 #include "grglyph.h"
26 #include "gtest.h"
27 #include "gassert.h"
28 #include <algorithm>
29 
31  m_type(G::Test::enabled("contiguous")?Contiguous:type) ,
32  m_dx(0) ,
33  m_dy(0) ,
34  m_channels(0) ,
35  m_data(m_data_store) ,
36  m_rows_set(false)
37 {
38  G_ASSERT( valid() ) ;
39 }
40 
41 Gr::ImageData::ImageData( ImageBuffer & image_buffer , Type type ) :
42  m_type(type) ,
43  m_dx(0) ,
44  m_dy(0) ,
45  m_channels(0) ,
46  m_data(image_buffer) ,
47  m_rows_set(false)
48 {
49  G_ASSERT( image_buffer.empty() ) ;
50 }
51 
52 Gr::ImageData::ImageData( ImageBuffer & image_buffer , int dx , int dy , int channels ) :
53  m_type(image_buffer.size()>1U?Segmented:Contiguous) ,
54  m_dx(dx) ,
55  m_dy(dy) ,
56  m_channels(channels) ,
57  m_data(image_buffer) ,
58  m_rows_set(false)
59 {
60  G_ASSERT( image_buffer.empty() || ( dx > 0 && dy > 0 && (channels==1 || channels==3) ) ) ;
61  G_ASSERT( image_buffer.empty() || image_buffer.size() == 1U || image_buffer.size() == sizet(dy) ) ;
62  G_ASSERT( image_buffer.empty() || image_buffer.at(0U).size() == imagebuffer::size_of(image_buffer) || image_buffer.at(0U).size() == sizet(dx,channels) ) ;
63  G_ASSERT( imagebuffer::size_of(image_buffer) == sizet(dx,dy,channels) ) ;
64 
65  if( image_buffer.empty() && dx != 0 && dy != 0 )
66  throw Error( "empty image buffer" ) ;
67 
68  if( dx < 0 || dy < 0 || !(image_buffer.empty() || channels==1 || channels==3) )
69  throw Error( "invalid dimensions" ) ;
70 
71  if( !image_buffer.empty() && image_buffer.size() != 1U && image_buffer.size() != sizet(dy) )
72  throw Error( "incorrect image buffer size" ) ;
73 }
74 
76 {
77 }
78 
79 Gr::ImageData::Type Gr::ImageData::type() const
80 {
81  return m_type ;
82 }
83 
85 {
86  return m_dx == 0 || m_dy == 0 || m_channels == 0 ;
87 }
88 
89 void Gr::ImageData::resize( int dx , int dy , int channels )
90 {
91  G_ASSERT( dx > 0 && dy > 0 ) ;
92  G_ASSERT( channels == 1 || channels == 3 ) ;
93 
94  if( dx == m_dx && dy == m_dy && channels == m_channels )
95  return ;
96 
97  m_dx = dx ;
98  m_dy = dy ;
99  m_channels = channels ;
100 
101  if( m_type == Contiguous )
102  {
103  m_data.resize( 1U ) ;
104  m_data[0].resize( sizet(m_dx,m_dy,m_channels) ) ;
105  }
106  else
107  {
108  m_data.clear() ;
109  m_data.reserve( m_dy ) ;
110  const size_t rowsize = sizet(m_dx,m_channels) ;
111  for( int y = 0 ; y < m_dy ; y++ )
112  {
113  m_data.push_back( std::vector<char>() ) ; // ie. emplace_back(rowsize)
114  m_data.back().resize( rowsize ) ;
115  }
116  }
117  m_rows_set = false ;
118  G_ASSERT( valid() ) ;
119 }
120 
121 void Gr::ImageData::setRows() const
122 {
123  // set up row pointers lazily
124  if( !m_rows_set )
125  {
126  m_rows.resize( m_dy ) ;
127  if( m_type == Contiguous )
128  {
129  const unsigned char * row_p = storerow(0) ;
130  const size_t rowsize = sizet( m_dx , m_channels ) ;
131  for( int y = 0 ; y < m_dy ; y++ , row_p += rowsize )
132  m_rows[y] = const_cast<unsigned char*>(row_p) ;
133  }
134  else
135  {
136  for( int y = 0 ; y < m_dy ; y++ )
137  m_rows[y] = const_cast<unsigned char*>(storerow(y)) ;
138  }
139  }
140  m_rows_set = true ;
141 }
142 
143 unsigned char ** Gr::ImageData::rowPointers()
144 {
145  setRows() ;
146  return &m_rows[0] ;
147 }
148 
149 const unsigned char * const * Gr::ImageData::rowPointers() const
150 {
151  setRows() ;
152  return &m_rows[0] ;
153 }
154 
156 {
157  return reinterpret_cast<char**>(rowPointers()) ;
158 }
159 
160 bool Gr::ImageData::contiguous() const
161 {
162  return m_data.size() == 1U ;
163 }
164 
165 void Gr::ImageData::scale( int scale_in , bool monochrome_out , bool use_colourspace )
166 {
167  G_ASSERT( valid() ) ;
168  if( scale_in < 1 ) throw Error( "invalid scale factor" ) ;
169  if( scale_in == 1 && (monochrome_out?1:m_channels) == m_channels ) return ;
170  if( m_data.empty() ) throw Error( "empty" ) ;
171 
172  int x_new = 0 ;
173  int y_new = 0 ;
174  unsigned char * p_out = storerow( 0 ) ;
175  const size_t dp = sizet( m_channels , scale_in ) ;
176  for( int y = 0 ; y < m_dy ; y += scale_in , y_new++ )
177  {
178  const unsigned char * p_in = row( y ) ;
179  if( m_data.size() > 1U ) p_out = row( y_new ) ;
180  for( int x = x_new = 0 ; x < m_dx ; x += scale_in , p_in += dp , x_new++ )
181  {
182  if( monochrome_out && m_channels == 3 && use_colourspace )
183  {
184  *p_out++ = Gr::ColourSpace::y_int( p_in[0] , p_in[1] , p_in[2] ) ;
185  }
186  else if( monochrome_out || m_channels == 1 )
187  {
188  *p_out++ = p_in[0] ;
189  }
190  else
191  {
192  *p_out++ = p_in[0] ;
193  *p_out++ = p_in[1] ;
194  *p_out++ = p_in[2] ;
195  }
196  }
197  }
198  G_ASSERT( x_new == scaled(m_dx,scale_in) ) ;
199  G_ASSERT( y_new == scaled(m_dy,scale_in) ) ;
200  m_dx = x_new ;
201  m_dy = y_new ;
202  m_channels = monochrome_out ? 1 : m_channels ;
203 
204  if( m_data.size() == 1U )
205  {
206  m_data[0].resize( sizet(m_dx,m_dy,m_channels) ) ;
207  }
208  else
209  {
210  size_t drow = sizet( m_dx , m_channels ) ;
211  m_data.resize( m_dy ) ;
212  for( int y = 0 ; y < m_dy ; y++ )
213  m_data[y].resize( drow ) ;
214  }
215  m_rows_set = false ;
216  G_ASSERT( valid() ) ;
217 }
218 
219 void Gr::ImageData::copyRowOut( int y , std::vector<char> & out , int scale , bool monochrome_out , bool use_colourspace ) const
220 {
221  G_ASSERT( y >= 0 && y < m_dy ) ;
222  G_ASSERT( scale >= 1 ) ;
223  if( y < 0 || y >= m_dy || scale < 1 )
224  throw Error( "invalid parameters" ) ;
225 
226  if( scale == 1 && !monochrome_out )
227  {
228  std::memcpy( &out[0] , row(y) , rowsize() ) ;
229  }
230  else
231  {
232  out.resize( sizet(scaled(m_dx,scale),monochrome_out?1:m_channels) ) ;
233  const unsigned char * p_in = row( y ) ;
234  const unsigned char * p_in_end = p_in + rowsize() ;
235  unsigned char * p_out = reinterpret_cast<unsigned char*>(&out[0]) ;
236  size_t dp_in = sizet( scale , m_channels ) ;
237  for( ; p_in < p_in_end ; p_in += dp_in )
238  {
239  if( m_channels == 3 && monochrome_out && use_colourspace )
240  {
241  *p_out++ = Gr::ColourSpace::y_int( p_in[0] , p_in[1] , p_in[2] ) ;
242  }
243  else if( m_channels == 3 && monochrome_out )
244  {
245  *p_out++ = *p_in ;
246  }
247  else if( m_channels == 3 )
248  {
249  *p_out++ = p_in[0] ;
250  *p_out++ = p_in[1] ;
251  *p_out++ = p_in[2] ;
252  }
253  else
254  {
255  *p_out++ = *p_in ;
256  }
257  }
258  }
259 }
260 
261 void Gr::ImageData::copyRowIn( int y , const unsigned char * p_in , size_t n_in , int channels_in , bool use_colourspace , int scale )
262 {
263  G_ASSERT( y >= 0 && p_in != nullptr ) ;
264  G_ASSERT( y < m_dy ) ;
265  G_ASSERT( channels_in == 1 || channels_in == 3 ) ;
266  G_ASSERT( scale >= 1 ) ;
267  if( p_in == nullptr || y < 0 || y >= m_dy || !(channels_in==1||channels_in==3) || scale < 1 )
268  throw Error( "invalid row parameter" ) ;
269  if( m_dx != scaled(int(n_in)/channels_in,scale) )
270  throw Error( "invalid row copy" ) ;
271 
272  copyRowInImp( row(y) , p_in , channels_in , use_colourspace , scale ) ;
273 }
274 
275 void Gr::ImageData::copyRowInImp( unsigned char * row_out , const unsigned char * row_in , int channels_in , bool use_colourspace , int scale )
276 {
277  if( m_channels == 3 && channels_in == 1 )
278  {
279  // more channels -- triple-up
280  int x_out = 0 ;
281  const int drow_out = m_dx * 3 ;
282  for( int x_in = 0 ; x_out < drow_out ; x_in += scale , x_out += 3 )
283  {
284  row_out[x_out+0] = row_out[x_out+1] = row_out[x_out+2] = row_in[x_in] ;
285  }
286  }
287  else if( m_channels == 1 && channels_in == 3 )
288  {
289  // fewer channels -- use first channel or do a colourspace transform
290  int x_out = 0 ;
291  const int dx_in = 3 * scale ;
292  for( int x_in = 0 ; x_out < m_dx ; x_in += dx_in , x_out++ )
293  {
294  row_out[x_out] = use_colourspace ? Gr::ColourSpace::y_int(row_in[x_in],row_in[x_in+1],row_in[x_in+2]) : row_in[x_in] ;
295  }
296  }
297  else if( scale > 1 && m_channels == 1 )
298  {
299  int x_out = 0 ;
300  for( int x_in = 0 ; x_out < m_dx ; x_in += scale , x_out++ )
301  {
302  row_out[x_out] = row_in[x_in] ;
303  }
304  }
305  else if( scale > 1 )
306  {
307  int x_out = 0 ;
308  const int dx_in = 3 * scale ;
309  int drow_out = m_dx * 3 ;
310  for( int x_in = 0 ; x_out < drow_out ; x_in += dx_in , x_out += 3 )
311  {
312  row_out[x_out+0] = row_in[x_in+0] ;
313  row_out[x_out+1] = row_in[x_in+1] ;
314  row_out[x_out+2] = row_in[x_in+2] ;
315  }
316  }
317  else
318  {
319  std::memcpy( row_out , row_in , sizet(m_dx,m_channels) ) ;
320  }
321 }
322 
323 void Gr::ImageData::copyIn( const char * data_in , size_t size_in , int dx_in , int dy_in , int channels_in , bool use_colourspace , int scale )
324 {
325  G_ASSERT( scale >= 1 ) ;
326  G_ASSERT( channels_in==1 || channels_in==3 ) ;
327  G_ASSERT( dx_in > 0 && dy_in > 0 ) ;
328  G_ASSERT( data_in != nullptr && size_in != 0U ) ;
329 
330  if( scale < 1 || data_in == nullptr || size_in == 0U || dx_in <= 0 || dy_in <= 0 || !( channels_in == 1 || channels_in == 3 ) )
331  throw Error( "invalid parameter" ) ;
332 
333  if( size_in != sizet(dx_in,dy_in,channels_in) || scaled(dx_in,scale) != m_dx || scaled(dy_in,scale) != m_dy )
334  throw Error( "copy-in size mismatch" ) ;
335 
336  const size_t drow_in = sizet( dx_in , channels_in , scale ) ;
337  const unsigned char * p_in = reinterpret_cast<const unsigned char*>(data_in) ;
338  for( int y = 0 ; y < m_dy ; y++ , p_in += drow_in )
339  {
340  copyRowInImp( row(y) , p_in , channels_in , use_colourspace , scale ) ;
341  }
342 }
343 
344 void Gr::ImageData::copyIn( const ImageBuffer & data_in , int dx_in , int dy_in , int channels_in , bool use_colourspace , int scale )
345 {
346  G_ASSERT( scale >= 1 ) ;
347  G_ASSERT( channels_in==1 || channels_in==3 ) ;
348  G_ASSERT( !data_in.empty() ) ;
349  G_ASSERT( data_in.size() == sizet(dy_in) || data_in.size() == 1U ) ;
350  G_ASSERT( data_in.at(0U).size() == sizet(channels_in,dx_in) || data_in.at(0U).size() == sizet(channels_in,dx_in,dy_in) ) ;
351 
352  if( scale < 1 || ( data_in.size() != sizet(dy_in) && data_in.size() != 1U ) || data_in.empty() ||
353  ( data_in.at(0U).size() != sizet(channels_in,dx_in) && data_in.at(0U).size() != sizet(channels_in,dx_in,dy_in) ) ||
354  !(channels_in==1 || channels_in==3) )
355  throw Error( "invalid parameter" ) ;
356 
357  if( scaled(dx_in,scale) != m_dx || scaled(dy_in,scale) != m_dy )
358  throw Error( "copy-in size mismatch" ) ;
359 
360  if( data_in.size() == 1U ) // contiguous
361  {
362  const size_t drow_in = sizet(scale,dx_in,channels_in) ;
363  const unsigned char * row_in = reinterpret_cast<const unsigned char*>(&(data_in.at(0))[0]) ;
364  for( int y_out = 0 ; y_out < m_dy ; y_out++ , row_in += drow_in )
365  {
366  copyRowInImp( row(y_out) , row_in , channels_in , use_colourspace , scale ) ;
367  }
368  }
369  else
370  {
371  int y_in = 0 ;
372  for( int y_out = 0 ; y_out < m_dy ; y_out++ , y_in += scale )
373  {
374  const unsigned char * row_in = reinterpret_cast<const unsigned char*>(&(data_in.at(y_in))[0]) ;
375  copyRowInImp( row(y_out) , row_in , channels_in , use_colourspace , scale ) ;
376  }
377  }
378 }
379 
380 void Gr::ImageData::copyTo( std::vector<char> & out ) const
381 {
382  out.resize( size() ) ;
383  if( contiguous() && m_dy > 0 )
384  {
385  std::memcpy( &out[0] , storerow(0) , size() ) ;
386  }
387  else
388  {
389  char * out_p = &out[0] ;
390  const size_t drow = sizet( m_dx , m_channels ) ;
391  for( int y = 0 ; y < m_dy ; y++ , out_p += drow )
392  {
393  std::memcpy( out_p , row(y) , drow ) ;
394  }
395  }
396 }
397 
399 {
401  imagebuffer::resize( out , m_dx , m_dy , m_channels ) ;
402  const size_t drow = rowsize() ;
403  int y = 0 ;
404  row_iterator const end = imagebuffer::row_end( out ) ;
405  for( row_iterator p = imagebuffer::row_begin(out) ; p != end ; ++p , y++ )
406  {
407  std::memcpy( imagebuffer::row_ptr(p) , row(y) , drow ) ;
408  }
409 }
410 
411 unsigned char * Gr::ImageData::p()
412 {
413  if( m_dy == 0 )
414  throw Error( "empty" ) ;
415  if( !contiguous() )
416  throw Error( "not contiguous" ) ;
417  return storerow(0) ;
418 }
419 
420 const unsigned char * Gr::ImageData::p() const
421 {
422  if( m_dy == 0 )
423  throw Error( "empty" ) ;
424  if( !contiguous() )
425  throw Error( "not contiguous" ) ;
426  return storerow(0) ;
427 }
428 
429 size_t Gr::ImageData::size() const
430 {
431  return sizet( m_dx , m_dy , m_channels ) ;
432 }
433 
435 {
436  return sizet( m_dx , m_channels ) ;
437 }
438 
439 void Gr::ImageData::fill( unsigned char r , unsigned char g , unsigned char b )
440 {
441  if( (g==r && b==r ) || m_channels == 1 )
442  {
443  size_t n = rowsize() ;
444  for( int y = 0 ; y < m_dy ; y++ )
445  std::memset( row(y) , r , n ) ;
446  }
447  else
448  {
449  for( int y = 0 ; y < m_dy ; y++ )
450  {
451  for( int x = 0 ; x < m_dx ; x++ )
452  rgb( x , y , r , g , b ) ;
453  }
454  }
455 }
456 
457 template <typename Fn>
458 void Gr::ImageData::modify( Fn fn )
459 {
460  const size_t drow = rowsize() ;
461  for( int y = 0 ; y < m_dy ; y++ )
462  {
463  unsigned char * p = row( y ) ;
464  for( size_t i = 0U ; i < drow ; i++ , p++ )
465  {
466  fn( *p ) ;
467  }
468  }
469 }
470 
471 template <typename Fn>
472 void Gr::ImageData::modify( const Gr::ImageData & other , Fn fn )
473 {
474  int dy = std::min( m_dy , other.m_dy ) ;
475  int dx = std::min( m_dx , other.m_dx ) ;
476  if( m_channels == other.m_channels )
477  {
478  const size_t drow = sizet( dx , m_channels ) ;
479  for( int y = 0 ; y < dy ; y++ )
480  {
481  unsigned char * p_this = row( y ) ;
482  const unsigned char * p_other = other.row( y ) ;
483  for( size_t i = 0U ; i < drow ; i++ , p_this++ , p_other++ )
484  {
485  fn( *p_this , *p_other ) ;
486  }
487  }
488  }
489  else if( m_channels == 1 && other.m_channels == 3 )
490  {
491  const size_t drow = sizet( dx ) ;
492  for( int y = 0 ; y < dy ; y++ )
493  {
494  unsigned char * p_this = row( y ) ;
495  const unsigned char * p_other = other.row( y ) ;
496  for( size_t i = 0U ; i < drow ; i++ , p_this++ , p_other += 3 )
497  {
498  fn( *p_this , p_other[0] , p_other[1] , p_other[2] ) ;
499  }
500  }
501  }
502  else if( m_channels == 3 && other.m_channels == 1 )
503  {
504  const size_t drow = sizet( dx , 3 ) ;
505  for( int y = 0 ; y < dy ; y++ )
506  {
507  unsigned char * p_this = row( y ) ;
508  const unsigned char * p_other = other.row( y ) ;
509  int c = 0 ;
510  for( size_t i = 0U ; i < drow ; i++ , p_this++ )
511  {
512  fn( *p_this , *p_other ) ;
513  c = c == 0 ? 1 : ( c == 1 ? 2 : 0 ) ;
514  if( c == 0 ) ++p_other ;
515  }
516  }
517  }
518 }
519 
520 template <typename Fn>
521 void Gr::ImageData::modify( const Gr::ImageData & data_1 , const Gr::ImageData & data_2 , Fn fn )
522 {
523  if( m_dx != data_1.m_dx ||
524  m_dy != data_1.m_dy ||
525  m_channels != data_1.m_channels ||
526  data_1.m_dx != data_2.m_dx ||
527  data_1.m_dy != data_2.m_dy ||
528  data_1.m_channels != data_2.m_channels )
529  throw ImageData::Error( "mismatched image sizes" ) ;
530 
531  const size_t drow = sizet( m_dx , m_channels ) ;
532  for( int y = 0 ; y < m_dy ; y++ )
533  {
534  unsigned char * p_this = row( y ) ;
535  const unsigned char * p_1 = data_1.row( y ) ;
536  const unsigned char * p_2 = data_2.row( y ) ;
537  for( size_t i = 0U ; i < drow ; i++ , p_this++ , p_1++ , p_2++ )
538  {
539  fn( *p_this , *p_1 , *p_2 ) ;
540  }
541  }
542 }
543 
544 namespace
545 {
546  struct Shift
547  {
548  Shift( unsigned int n ) : m_n(n) {}
549  void operator()( unsigned char & c ) const
550  {
551  c >>= m_n ;
552  }
553  unsigned int m_n ;
554  } ;
555 }
556 
557 void Gr::ImageData::dim( unsigned int shift )
558 {
559  modify( Shift(shift) ) ;
560 }
561 
562 namespace
563 {
564  struct Fractionate
565  {
566  Fractionate( unsigned int n , unsigned int d ) : m_n(n) , m_d(d) {}
567  void operator()( unsigned char & c ) const
568  {
569  unsigned int cc = c ;
570  cc *= m_n ;
571  cc /= m_d ;
572  cc = std::min( 255U , cc ) ;
573  c = static_cast<unsigned char>(cc) ;
574  }
575  unsigned int m_n ;
576  unsigned int m_d ;
577  } ;
578 }
579 
580 void Gr::ImageData::dim( unsigned int numerator , unsigned int denominator )
581 {
582  if( numerator == 0U )
583  fill( 0 , 0 , 0 ) ;
584  else if( denominator != 0U && numerator != denominator )
585  modify( Fractionate(numerator,denominator) ) ;
586 }
587 
588 namespace
589 {
590  struct Mix
591  {
592  Mix( unsigned int n1 , unsigned int n2 , unsigned int d ) : m_n1(n1) , m_n2(n2) , m_d(d?d:1U) {}
593  void operator()( unsigned char & c , unsigned char c_1 , unsigned char c_2 ) const
594  {
595  unsigned int cc_1 = c_1 ;
596  cc_1 *= m_n1 ;
597  cc_1 /= m_d ;
598  cc_1 = std::min( 255U , cc_1 ) ;
599 
600  unsigned int cc_2 = c_2 ;
601  cc_2 *= m_n2 ;
602  cc_2 /= m_d ;
603  cc_2 = std::min( 255U , cc_2 ) ;
604 
605  unsigned int cc = cc_1 + cc_2 ;
606  cc = std::min( 255U , cc ) ;
607 
608  c = static_cast<unsigned char>(cc) ;
609  }
610  unsigned int m_n1 ;
611  unsigned int m_n2 ;
612  unsigned int m_d ;
613  } ;
614 }
615 
616 void Gr::ImageData::mix( const ImageData & data_1 , const ImageData & data_2 , unsigned int n1 , unsigned n2 , unsigned int d )
617 {
618  G_ASSERT( d != 0 ) ;
619  resize( data_1.dx() , data_1.dy() , data_1.channels() ) ;
620  modify( data_1 , data_2 , Mix(n1,n2,d) ) ;
621 }
622 
623 namespace
624 {
625  struct Add
626  {
627  void operator()( unsigned char & c_this , unsigned char c_other ) const
628  {
629  const unsigned int n = c_this + c_other ;
630  c_this = std::min( n , 255U ) ;
631  }
632  void operator()( unsigned char & c_this , unsigned char c0 , unsigned char c1 , unsigned char c2 ) const
633  {
634  this->operator()( c_this , Gr::ColourSpace::y_int(c0,c1,c2) ) ;
635  }
636  } ;
637 }
638 
639 void Gr::ImageData::add( const Gr::ImageData & other )
640 {
641  modify( other , Add() ) ;
642 }
643 
644 namespace
645 {
646  struct Subtract
647  {
648  void operator()( unsigned char & c_this , unsigned char c_other )
649  {
650  const unsigned int n = c_this > c_other ? static_cast<unsigned int>(c_this-c_other) : 0U ;
651  c_this = std::min( n , 255U ) ;
652  }
653  void operator()( unsigned char & c_this , unsigned char c0 , unsigned char c1 , unsigned char c2 )
654  {
655  this->operator()( c_this , Gr::ColourSpace::y_int(c0,c1,c2) ) ;
656  }
657  } ;
658 }
659 
661 {
662  modify( other , Subtract() ) ;
663 }
664 
665 void Gr::ImageData::crop( int dx_new , int dy_new )
666 {
667  if( dx_new >= m_dx && dy_new >= m_dy )
668  return ; // no-op
669 
670  dx_new = std::min( dx_new , m_dx ) ;
671  dy_new = std::min( dy_new , m_dy ) ;
672 
673  const int top = (m_dy-dy_new) / 2 ;
674  const int left = (m_dx-dx_new) / 2 ;
675 
676  const size_t dleft = sizet( left , m_channels ) ;
677  const size_t dtop = sizet( top , m_dx , m_channels ) ;
678  const size_t drow_out = sizet( dx_new , m_channels ) ;
679  const size_t drow_in = sizet( m_dx , m_channels ) ;
680 
681  if( contiguous() )
682  {
683  const unsigned char * row_in = storerow(0) + dtop ;
684  unsigned char * row_out = storerow(0) ;
685  for( int y = 0 ; y < dy_new ; y++ , row_in += drow_in , row_out += drow_out )
686  {
687  std::memmove( row_out , row_in+dleft , drow_out ) ;
688  }
689  m_data[0].resize( sizet(dx_new,dy_new,m_channels) ) ;
690  }
691  else
692  {
693  std::rotate( m_data.begin() , m_data.begin() + top , m_data.end() ) ;
694  m_data.resize( dy_new ) ;
695  for( int y = 0 ; y < dy_new ; y++ )
696  {
697  unsigned char * p_out = storerow( y ) ;
698  std::memmove( p_out , p_out+dleft , drow_out ) ;
699  m_data[y].resize( drow_out ) ;
700  }
701  }
702 
703  m_dx = dx_new ;
704  m_dy = dy_new ;
705  m_rows_set = false ;
706  G_ASSERT( valid() ) ;
707 }
708 
709 void Gr::ImageData::expand( int dx_new , int dy_new )
710 {
711  if( dx_new <= m_dx && dy_new <= m_dy )
712  return ; // no-op
713 
714  dx_new = std::max( dx_new , m_dx ) ;
715  dy_new = std::max( dy_new , m_dy ) ;
716 
717  const int top = (dy_new-m_dy) / 2 ;
718  const int left = (dx_new-m_dx) / 2 ;
719  const int right = dx_new - m_dx - left ;
720 
721  const size_t dleft = sizet( left , m_channels ) ;
722  const size_t dright = sizet( right , m_channels ) ;
723  const size_t drow_in = sizet( m_dx , m_channels ) ;
724  const size_t drow_out = sizet( dx_new , m_channels ) ;
725 
726  if( contiguous() )
727  {
728  m_data[0].resize( sizet(dx_new,dy_new,m_channels) ) ;
729  const unsigned char * p_in = storerow(0) + (m_dy-1)*m_dx*m_channels ;
730  unsigned char * p_out = storerow(0) + (top+(m_dy-1))*dx_new*m_channels ;
731  for( int y = m_dy-1 ; y >= 0 ; y-- , p_in -= drow_in , p_out -= drow_out )
732  {
733  std::memmove( p_out+dleft , p_in , drow_in ) ;
734  std::memset( p_out , 0 , dleft ) ;
735  std::memset( p_out+dleft+drow_in , 0 , dright ) ;
736  }
737  p_out = storerow(0) ;
738  for( int y = 0 ; y < dy_new ; y++ , p_out += drow_out )
739  {
740  if( y < top ) std::memset( p_out , 0 , drow_out ) ;
741  }
742  }
743  else
744  {
745  m_data.resize( dy_new ) ;
746  for( int y = m_dy ; y < dy_new ; y++ )
747  m_data[y].resize( dx_new ) ;
748  std::rotate( m_data.begin() , m_data.begin()+m_dy , m_data.begin()+m_dy+top ) ; // pic-top-bottom -> top-pic-bottom
749  for( int y = 0 ; y < dy_new ; y++ )
750  {
751  m_data[y].resize( drow_out ) ;
752  unsigned char * row = storerow( y ) ;
753  std::memmove( row+dleft , row , drow_in ) ;
754  std::memset( row , 0 , dleft ) ;
755  }
756  }
757 
758  m_dx = dx_new ;
759  m_dy = dy_new ;
760  m_rows_set = false ;
761  G_ASSERT( valid() ) ;
762 }
763 
764 namespace
765 {
766  struct GlyphOut
767  {
768  GlyphOut( Gr::ImageData & image_data , int x , int y ,
769  Gr::Colour foreground , Gr::Colour background , bool draw_background ) :
770  m_image_data(image_data) ,
771  m_x(x) ,
772  m_y(y) ,
773  m_foreground(foreground) ,
774  m_background(background) ,
775  m_draw_background(draw_background)
776  {
777  }
778  void operator()( int x_in , int y_in , bool b )
779  {
780  int x = m_x + x_in ;
781  int y = m_y + y_in ;
782  if( x >= 0 && x < m_image_data.dx() &&
783  y >= 0 && y < m_image_data.dy() )
784  {
785  if( b )
786  m_image_data.rgb( x , y , m_foreground.r() , m_foreground.g() , m_foreground.b() ) ;
787  else if( m_draw_background )
788  m_image_data.rgb( x , y , m_background.r() , m_background.g() , m_background.b() ) ;
789  }
790  }
791  Gr::ImageData & m_image_data ;
792  int m_x ;
793  int m_y ;
794  Gr::Colour m_foreground ;
795  Gr::Colour m_background ;
796  bool m_draw_background ;
797  } ;
798 }
799 
801  Colour foreground , Colour background , bool draw_background , bool wrap_on_nl )
802 {
803  return ImageDataWriter( *this , x , y , foreground , background , draw_background , wrap_on_nl ) ;
804 }
805 
806 void Gr::ImageData::write( char c , int x , int y ,
807  Gr::Colour foreground , Gr::Colour background , bool draw_background )
808 {
809  GlyphOut out( *this , x , y , foreground , background , draw_background ) ;
810  Glyph::output( std::string(1U,c) , out ) ;
811 }
812 
813 namespace
814 {
815  struct checker
816  {
817  checker( bool & ok ) : m_ok(ok) {}
818  void operator()( bool checked_ok ) { G_ASSERT(checked_ok) ; if(!checked_ok) m_ok = false ; }
819  bool & m_ok ;
820  } ;
821 }
822 
823 bool Gr::ImageData::valid() const
824 {
825  // this is only used in a debug build to check class invariants...
826 
827  bool ok = true ;
828  checker check( ok ) ;
829 
830  if( m_dx == 0 || m_dx == 0 || m_channels == 0 || m_data.empty() )
831  {
832  check( m_dx == 0 ) ;
833  check( m_dy == 0 ) ;
834  check( m_channels == 0 ) ;
835  check( m_data.empty() ) ;
836  check( !m_rows_set ) ;
837  }
838  else
839  {
840  check( m_dx > 0 && m_dy > 0 && m_channels > 0 ) ;
841  check( m_data.empty() || m_data.size() == 1U || m_data.size() == sizet(m_dy) ) ;
842  if( m_data.empty() )
843  {
844  check( m_rows.empty() ) ;
845  }
846  else if( m_data.size() == 1U )
847  {
848  check( m_data[0].size() == sizet(m_dx,m_dy,m_channels) ) ;
849  check( !m_rows_set || m_rows.size() == sizet(m_dy) ) ;
850  check( !m_rows_set || m_rows[0] == storerow(0) ) ;
851  check( contiguous() ) ;
852  }
853  else
854  {
855  check( m_data[0].size() == sizet(m_dx,m_channels) ) ;
856  check( m_data[1].size() == sizet(m_dx,m_channels) ) ;
857  if( m_rows_set )
858  {
859  check( m_rows.size() == sizet(m_dy) ) ;
860  for( size_t y = 0 ; y < std::min(m_rows.size(),sizet(m_dy)) ; y++ )
861  check( m_rows.at(y) == storerow(y) ) ;
862  }
863  }
864  }
865  return ok ;
866 }
867 
868 // ==
869 
871  Colour foreground , Colour background , bool draw_background , bool wrap_on_nl ) :
872  m_data(&data),
873  m_dx(data.dx()),
874  m_dy(data.dy()),
875  m_x0(x) ,
876  m_x(x),
877  m_y(y),
878  m_foreground(foreground) ,
879  m_background(background) ,
880  m_draw_background(draw_background) ,
881  m_wrap_on_nl(wrap_on_nl)
882 {
883 }
884 
885 bool Gr::ImageDataWriter::pre( char c )
886 {
887  if( m_wrap_on_nl && c == '\n' )
888  {
889  m_x = m_x0 ;
890  m_y += 9 ;
891  return false ;
892  }
893  else
894  {
895  return true ;
896  }
897 }
898 
899 void Gr::ImageDataWriter::post( char c )
900 {
901  m_x += 9 ;
902  if( (m_x+8) > m_dx )
903  {
904  m_x = m_x0 ;
905  m_y += 9 ;
906  }
907 }
908 
909 /// \file grimagedata.cpp
triple< unsigned char > rgb(triple< unsigned char > yuv) g__noexcept
A top-level function that calculates rgb from yuv with default implementation options.
size_t rowsize() const
Returns the row size, ie. dx()*channels().
int dx() const
Returns the width.
Definition: grimagedata.h:329
A traits class that can be specialised for Gr::ImageBuffer candidates.
Definition: grtraits.h:34
A holder for image data, having eight bits per sample and one or three channels.
Definition: grimagedata.h:46
void mix(const ImageData &_1, const ImageData &_2, unsigned int numerator_1, unsigned int numerator_2, unsigned int denominator)
Creates a mixed image from two equally-shaped sources images.
size_t size() const
Returns the total image size, ie. dx()*dy()*channels().
void resize(int dx, int dy, int channels)
Resizes the image data.
Definition: grimagedata.cpp:89
void expand(int dx, int dy)
Expands the image so that the new image has the dimensions given, with the original image centered in...
Type type() const
Returns the contiguous/segmented enumeration.
Definition: grimagedata.cpp:79
unsigned char * row(int y)
Returns a pointer to the y'th row.
Definition: grimagedata.h:368
void scale(int factor, bool monochrome, bool use_colourspace)
Scales-down the image by sub-sampling.
A simple rgb colour structure.
Definition: grcolour.h:36
Vectors ImageBuffer
An ImageBuffer is used to hold raw image data, typically in more than one chunk.
Definition: grimagebuffer.h:47
bool empty() const
Returns true if the size() is zero.
Definition: grimagedata.cpp:84
void subtract(const ImageData &other)
Subtracts the given image data from this.
~ImageData()
Destructor.
Definition: grimagedata.cpp:75
unsigned char ** rowPointers()
Returns a pointer to the array of row pointers.
void copyTo(std::vector< char > &out) const
Copies the image to the given output buffer, resizing the output vector as necessary.
ImageDataWriter(Gr::ImageData &data, int x, int y, Gr::Colour foreground, Gr::Colour background, bool draw_background, bool wrap_on_nl)
Constructor.
void copyIn(const char *data_in, size_t data_size_in, int dx_in, int dy_in, int channels_in, bool use_colourspace=false, int scale=1)
Copies the image in from a raw buffer, with channel-count adjustment and optional scaling...
int channels() const
Returns the number of channels (zero, one or three).
Definition: grimagedata.h:341
void copyRowIn(int y, const unsigned char *row_buffer_in, size_t row_buffer_in_size, int channels_in, bool use_colourspace=false, int scale=1)
Sets a row of pixels by copying, with channel-count adjustment and optional scaling.
unsigned char y_int(unsigned char r, unsigned char g, unsigned char b)
A fast conversion from rgb to y.
void dim(unsigned int shift)
Dims the image by right-shifting all pixel values.
const unsigned char * p() const
Returns a const pointer to the image data, but throws if the data is not contiguous.
static void output(const std::string &s, Tout &out_functor)
Calls an (x,y,bool) functor for all the glyph points corresponding to the given line of text...
Definition: grglyph.h:67
ImageData(Type=Segmented)
Default constructor for a zero-size image.
Definition: grimagedata.cpp:30
void fill(unsigned char r, unsigned char g, unsigned char b)
Fills the image with a solid colour as if calling rgb() for every pixel.
ImageDataWriter writer(int x, int y, Colour foreground, Colour background, bool draw_background, bool wrap_on_nl)
Returns a functor that calls write().
void crop(int dx, int dy)
Crops the image so that it fits inside the given dimensions.
void copyRowOut(int y, std::vector< char > &out, int scale=1, bool monochrome_out=false, bool use_colourspace=true) const
Copies a row into the given output buffer, resizing the output vector as necessary.
void add(const ImageData &other)
Adds the given image data to this.
int dy() const
Returns the height.
Definition: grimagedata.h:335
void write(char c, int x, int y, Colour foreground, Colour background, bool draw_background)
Draws a latin-1 character into the image at the given position.