libcrn  3.9.5
A document image processing library
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
CRNImageGray.cpp
Go to the documentation of this file.
1 /* Copyright 2006-2016 Yann LEYDIER, CoReNum, INSA-Lyon, ENS-Lyon
2  *
3  * This file is part of libcrn.
4  *
5  * libcrn is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU Lesser General Public License as published by
7  * the Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * libcrn is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  * GNU Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public License
16  * along with libcrn. If not, see <http://www.gnu.org/licenses/>.
17  *
18  * file: CRNImageGray.cpp
19  * \author Yann LEYDIER
20  */
21 
22 #include <CRNImage/CRNImageGray.h>
23 #include <CRNData/CRNInt.h>
24 #include <CRNData/CRNDataFactory.h>
25 #include <CRNBlock.h>
26 #include <CRNException.h>
27 #include <CRNIO/CRNFileShield.h>
28 #include <CRNi18n.h>
29 
30 #ifdef CRN_USING_GDKPB
31 # include <gdk-pixbuf/gdk-pixbuf.h>
32 #endif
33 
34 #ifdef CRN_USING_GDIPLUS
35 # include <windows.h>
36 # include <objidl.h>
37 # include <gdiplus.h>
38 # undef RegisterClass
39 #endif
40 
41 #ifdef CRN_USING_LIBPNG
42 # include <png.h>
43 #endif
44 
45 #ifdef CRN_USING_LIBJPEG
46 # include <jpeglib.h>
47 # include <setjmp.h>
48 struct crn_jpeg_error_mgr {
49  struct jpeg_error_mgr pub; /* "public" fields */
50  jmp_buf setjmp_buffer; /* for return to caller */
51 };
52 typedef struct crn_jpeg_error_mgr * crn_jpeg_error_ptr;
53 void crn_jpeg_error_exit(j_common_ptr cinfo);
54 #endif
55 
56 void fclose_if_not_null(FILE *f);
57 
58 using namespace crn;
59 
60 #ifdef CRN_USING_LIBPNG
61 static std::pair<bool, String> save_png_libpng(const Path &filename, const ImageGray &img)
62 {
63  // libpng does not support URIs
64  Path fname(filename);
65  fname.ToLocal();
66 
67  std::unique_ptr<FILE, decltype(fclose_if_not_null)*> fp(fopen(fname.CStr(), "wb"), fclose_if_not_null);
68  if (!fp)
69  {
70  return std::make_pair(false, String(_("Cannot create file ")) + U"<" + fname + U">");
71  }
72 
73  /* Create png struct & info */
74  png_structp png_ptr = png_create_write_struct (PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
75  if (!png_ptr)
76  {
77  return std::make_pair(false, String(_("Cannot create the PNG structure.")));
78  }
79  png_infop info_ptr = png_create_info_struct(png_ptr);
80  if (!info_ptr)
81  {
82  png_destroy_write_struct(&png_ptr, (png_infopp)NULL);
83  return std::make_pair(false, String(_("Cannot create the PNG info.")));
84  }
85 
86  /* Set up the error handling */
87  if (setjmp(png_jmpbuf(png_ptr)))
88  {
89  png_destroy_write_struct(&png_ptr, &info_ptr);
90  return std::make_pair(false, String(_("Error while generating the PNG image.")));
91  }
92 
93  /* setup libpng for using standard C fwrite() function with our FILE pointer */
94  png_init_io(png_ptr, fp.get());
95 
96  /* Fill in the png_info structure */
97  int width = int(img.GetWidth());
98  int height = int(img.GetHeight());
99  int bit_depth = 8;
100  int color_type = PNG_COLOR_TYPE_GRAY;
101  int interlace_type = PNG_INTERLACE_NONE;
102  int compression_type = PNG_COMPRESSION_TYPE_DEFAULT;
103  int filter_method = PNG_FILTER_TYPE_DEFAULT;
104  png_set_IHDR(png_ptr, info_ptr, width, height, bit_depth, color_type, interlace_type, compression_type, filter_method);
105 
106  /* Using the low-level write interface */
107  png_write_info(png_ptr, info_ptr);
108  png_bytepp row_pointers;
109  row_pointers = (png_bytep*)malloc(sizeof(png_byte*) * height);
110  for (int i = 0; i < height ; ++i)
111  {
112 #if (PNG_LIBPNG_VER > 10300)
113  row_pointers[i] = (png_bytep)calloc(1, png_get_rowbytes(png_ptr, info_ptr));
114 #else
115  row_pointers[i] = (png_bytep)calloc(1, info_ptr->rowbytes);
116 #endif
117  }
118 
119  /* Conversion */
120  //int channels = info_ptr->channels;
121  for (int y = 0; y < height; y++)
122  for (int x = 0; x < width; x++)
123  row_pointers[y][x] = img.At(x, y);
124 
125  png_write_image(png_ptr, row_pointers);
126 
127  // TODO more options
128  png_text txt[1];
129  txt[0].compression = PNG_TEXT_COMPRESSION_NONE;
130  txt[0].key = (char*)"creator";
131  txt[0].text = (char*)"libcrn";
132  txt[0].text_length = strlen(txt[0].text);
133  png_set_text(png_ptr, info_ptr, txt, 1);
134 
135  png_write_end(png_ptr, info_ptr);
136  png_destroy_write_struct(&png_ptr, &info_ptr);
137 
138  // free
139  for (int i = 0; i < height ; ++i)
140  {
141  free(row_pointers[i]);
142  }
143  free(row_pointers);
144  return std::make_pair(true, String(""));
145 
146 }
147 #endif
148 
149 #ifdef CRN_USING_GDKPB
150 static std::pair<bool, String> save_png_gdkpixbuf(const Path &fname, const ImageGray &img)
151 {
152  GdkPixbuf *pb = gdk_pixbuf_new(GDK_COLORSPACE_RGB, FALSE, 8,
153  int(img.GetWidth()), int(img.GetHeight()));
154  if (!pb)
155  {
156  return std::make_pair(false, String(_("Cannot create temporary buffer.")));
157  }
158 
159  int w = gdk_pixbuf_get_width(pb);
160  int h = gdk_pixbuf_get_height(pb);
161  int rs = gdk_pixbuf_get_rowstride(pb);
162  guchar *ppix = gdk_pixbuf_get_pixels(pb);
163  for (int y = 0; y < h; y++)
164  for (int x = 0; x < w; x++)
165  {
166  const auto px = img.At(x, y);
167  int o2 = x + x + x + y * rs;
168  ppix[o2] = px;
169  ppix[o2 + 1] = px;
170  ppix[o2 + 2] = px;
171  }
172 
173  GError *err = NULL;
174  gchar *utfname;
175  gsize i;
176  utfname = g_locale_to_utf8(fname.CStr(), -1, NULL, &i, NULL);
177  gchar *filename;
178  filename = g_filename_from_utf8(utfname, -1, NULL, &i, NULL);
179  g_free(utfname);
180  gchar *tinfo = g_locale_to_utf8("tEXt::Source", -1, NULL, &i, NULL);
181  gchar *tinfo2 = g_locale_to_utf8("Saved by libcrn.", -1, NULL, &i, NULL);
182  bool ok = gdk_pixbuf_save(pb, filename, "png", &err, tinfo, tinfo2, NULL);
183  g_free(filename);
184  g_free(tinfo);
185  g_free(tinfo2);
186  g_object_unref(pb);
187  String out(U"");
188  if (!ok)
189  {
190  out = String(_("Cannot save file. ")) + err->message;
191  g_error_free(err);
192  }
193  return std::make_pair(ok, out);
194 }
195 #endif
196 
197 #ifdef CRN_USING_GDIPLUS
198 static int GetEncoderClsid(const WCHAR* format, CLSID* pClsid)
199 {
200  UINT num = 0; // number of image encoders
201  UINT size = 0; // size of the image encoder array in bytes
202 
203  Gdiplus::ImageCodecInfo* pImageCodecInfo = NULL;
204 
205  Gdiplus::GetImageEncodersSize(&num, &size);
206  if (size == 0)
207  return -1; // Failure
208 
209  pImageCodecInfo = (Gdiplus::ImageCodecInfo*)(malloc(size));
210  if (pImageCodecInfo == nullptr)
211  return -1; // Failure
212 
213  Gdiplus::GetImageEncoders(num, size, pImageCodecInfo);
214 
215  for (UINT j = 0; j < num; ++j)
216  {
217  if (wcscmp(pImageCodecInfo[j].MimeType, format) == 0)
218  {
219  *pClsid = pImageCodecInfo[j].Clsid;
220  free(pImageCodecInfo);
221  return j; // Success
222  }
223  }
224 
225  free(pImageCodecInfo);
226  return -1; // Failure
227 }
228 
229 static std::pair<bool, String> save_png_gdiplus(const Path &filename, const ImageGray &img)
230 {
231  // gdi+ does not support URIs
232  Path winname(filename);
233  winname.ToWindows();
234  const auto newsize = winname.Size() + 1;
235  auto wcstring = std::vector<wchar_t>(newsize);
236  auto convertedChars = size_t(0);
237  mbstowcs_s(&convertedChars, wcstring.data(), newsize, winname.CStr(), _TRUNCATE);
238 
239  Gdiplus::Bitmap *outbm = new Gdiplus::Bitmap(INT(img.GetWidth()), INT(img.GetHeight()), PixelFormat8bppIndexed);
240  if (!outbm)
241  {
242  return std::make_pair(false, String(_("Cannot create bitmap")));
243  }
244  Gdiplus::ColorPalette *pal = (Gdiplus::ColorPalette *)malloc(sizeof(Gdiplus::ColorPalette) + 255 * sizeof(Gdiplus::ARGB));
245  pal->Count = 256;
246  pal->Flags = Gdiplus::PaletteFlagsGrayScale;
247  for (size_t tmp = 0; tmp < 256; ++tmp)
248  pal->Entries[tmp] = Gdiplus::ARGB(0xFF000000 | tmp | (tmp << 8) | (tmp << 16));
249  outbm->SetPalette(pal);
250  Gdiplus::BitmapData bitmapData;
251  outbm->LockBits(&Gdiplus::Rect(0, 0, outbm->GetWidth(), outbm->GetHeight()), Gdiplus::ImageLockModeWrite, PixelFormat8bppIndexed, &bitmapData);
252  auto *pixels = (uint8_t*)bitmapData.Scan0;
253  //#pragma omp parallel for
254  FOREACHPIXEL(x, y, img)
255  {
256  pixels[x + y * bitmapData.Stride] = img.At(x, y);
257  }
258  outbm->UnlockBits(&bitmapData);
259 
260  CLSID pngClsid;
261  GetEncoderClsid(L"image/png", &pngClsid);
262  Gdiplus::Status stat = outbm->Save(wcstring.data(), &pngClsid, nullptr);
263  delete outbm;
264  free(pal);
265  return std::make_pair(stat == Gdiplus::Ok, String{});
266 }
267 #endif
268 
277 void crn::impl::SavePNG(const ImageGray &img, const Path &fname)
278 {
279  if (!fname)
280  throw ExceptionInvalidArgument(StringUTF8("void Image::SavePNG(const Path &fname): ") + _("Null file name."));
281 
282  std::lock_guard<std::mutex> lock(crn::FileShield::GetMutex(fname)); // lock the file
283 
284  std::pair<bool, String> res(std::make_pair(false, String(U"")));
285  String error;
286 #ifdef CRN_USING_LIBPNG
287  if (res.first == false)
288  {
289  res = save_png_libpng(fname, img);
290  error += U" " + res.second;
291  }
292 #endif
293 #ifdef CRN_USING_GDIPLUS
294  if (res.first == false)
295  {
296  res = save_png_gdiplus(fname, img);
297  error += U" " + res.second;
298  }
299 #endif
300 #ifdef CRN_USING_GDKPB
301  if (res.first == false)
302  {
303  res = save_png_gdkpixbuf(fname, img);
304  error += U" " + res.second;
305  }
306 #endif
307  if (res.first == false)
308  throw ExceptionRuntime(StringUTF8("void crn::SavePNG(const ImageGray &img, const Path &fname): ") +
309  _("No library for saving image found or write permissions on the file or directory are not granted. No image will be saved.") + "\n" + StringUTF8(error) + "\n" + StringUTF8(fname));
310 }
311 
312 #ifdef CRN_USING_LIBJPEG
313 static std::pair<bool, String> save_jpeg_libjpeg(const Path &filename, const ImageGray &img, int qual)
314 {
315  // libjpeg does not support URIs
316  Path fname(filename);
317  fname.ToLocal();
318 
319  std::unique_ptr<FILE, decltype(fclose_if_not_null)*> fp(fopen(fname.CStr(), "wb"), fclose_if_not_null);
320  if (!fp)
321  {
322  return std::make_pair(false, String(_("Cannot create file ")) + U"<" + fname + U">");
323  }
324 
325  crn_jpeg_error_mgr jerr;
326  struct jpeg_compress_struct cinfo;
327 
328  cinfo.err = jpeg_std_error(&jerr.pub);
329  jerr.pub.error_exit = crn_jpeg_error_exit;
330  if (setjmp(jerr.setjmp_buffer))
331  {
332  jpeg_destroy_compress(&cinfo);
333  return std::make_pair(false, String(_("Cannot create jpeg file structure.")));
334  }
335 
336  jpeg_create_compress(&cinfo);
337  jpeg_stdio_dest(&cinfo, fp.get());
338  cinfo.image_width = int(img.GetWidth());
339  cinfo.image_height = int(img.GetHeight());
340  cinfo.input_components = 1;
341  cinfo.in_color_space = JCS_GRAYSCALE;
342  jpeg_set_defaults(&cinfo);
343  jpeg_set_quality(&cinfo, qual, TRUE);
344  jpeg_start_compress(&cinfo, TRUE);
345  JSAMPROW scanline = new JSAMPLE[img.GetWidth()];
346  for (size_t y = 0; y < img.GetHeight(); ++y)
347  {
348  for (size_t x = 0; x < img.GetWidth(); ++x)
349  {
350  scanline[x] = img.At(x, y);
351  }
352  jpeg_write_scanlines(&cinfo, &scanline, 1);
353  }
354  delete scanline;
355  // write comment
356  jpeg_write_marker(&cinfo, JPEG_COM, (JOCTET*)"Saved by libcrn.", (unsigned int)strlen("Saved by libcrn."));
357 
358  jpeg_finish_compress(&cinfo);
359  jpeg_destroy_compress(&cinfo);
360 
361  return std::make_pair(true, String(U""));
362 }
363 #endif
364 
365 #ifdef CRN_USING_GDKPB
366 static std::pair<bool, String> save_jpeg_gdkpixbuf(const Path &fname, const ImageGray &img, int qual)
367 {
368  GdkPixbuf *pb = gdk_pixbuf_new(GDK_COLORSPACE_RGB, FALSE, 8,
369  int(img.GetWidth()), int(img.GetHeight()));
370  if (!pb)
371  {
372  return std::make_pair(false, String(_("Cannot create temporary buffer.")));
373  }
374  int w = gdk_pixbuf_get_width(pb);
375  int h = gdk_pixbuf_get_height(pb);
376  int rs = gdk_pixbuf_get_rowstride(pb);
377  guchar *ppix = gdk_pixbuf_get_pixels(pb);
378  for (int y = 0; y < h; y++)
379  for (int x = 0; x < w; x++)
380  {
381  auto px = img.At(x, y);
382  int o2 = x + x + x + y * rs;
383  ppix[o2] = px;
384  ppix[o2 + 1] = px;
385  ppix[o2 + 2] = px;
386  }
387 
388  GError *err = NULL;
389  gchar *utfname;
390  gsize i;
391  utfname = g_locale_to_utf8(fname.CStr(), -1, NULL, &i, NULL);
392  gchar *filename;
393  filename = g_filename_from_utf8(utfname, -1, NULL, &i, NULL);
394  g_free(utfname);
395  gchar *tinfo = g_locale_to_utf8("quality", -1, NULL, &i, NULL);
396  char qt[16];
397  sprintf(qt, "%i", qual);
398  gchar *tinfo2 = g_locale_to_utf8(qt, -1, NULL, &i, NULL);
399  bool ok = gdk_pixbuf_save(pb, filename, "jpeg", &err, tinfo, tinfo2, NULL);
400  g_free(filename);
401  g_free(tinfo);
402  g_free(tinfo2);
403  g_object_unref(pb);
404  String res(U"");
405  if (!ok)
406  {
407  res = String(_("Cannot save file. ")) + err->message;
408  g_error_free(err);
409  }
410  return std::make_pair(ok, res);
411 }
412 #endif
413 
414 #ifdef CRN_USING_GDIPLUS
415 static std::pair<bool, String> save_jpeg_gdiplus(const Path &filename, const ImageGray &img, int qual)
416 {
417  // gdi+ does not support URIs
418  Path winname(filename);
419  winname.ToWindows();
420  const auto newsize = winname.Size() + 1;
421  auto wcstring = std::vector<wchar_t>(newsize);
422  auto convertedChars = size_t(0);
423  mbstowcs_s(&convertedChars, wcstring.data(), newsize, winname.CStr(), _TRUNCATE);
424 
425  Gdiplus::Bitmap *outbm = new Gdiplus::Bitmap(INT(img.GetWidth()), INT(img.GetHeight()), PixelFormat24bppRGB);
426  if (!outbm)
427  {
428  return std::make_pair(false, String(_("Cannot create bitmap")));
429  }
430  Gdiplus::BitmapData bitmapData;
431  auto clip = Gdiplus::Rect(0, 0, outbm->GetWidth(), outbm->GetHeight());
432  outbm->LockBits(&clip, Gdiplus::ImageLockModeWrite, PixelFormat24bppRGB, &bitmapData);
433  auto *pixels = (uint8_t*)bitmapData.Scan0;
434  //#pragma omp parallel for
435  FOREACHPIXEL(x, y, img)
436  {
437  size_t poffset = 3 * x + y * bitmapData.Stride;
438  const auto pix = img.At(x, y);
439  pixels[poffset + 2] = pix;
440  pixels[poffset + 1] = pix;
441  pixels[poffset] = pix;
442  }
443  outbm->UnlockBits(&bitmapData);
444  CLSID jpegClsid;
445  GetEncoderClsid(L"image/jpeg", &jpegClsid);
446  Gdiplus::EncoderParameters encoderParameters;
447  encoderParameters.Count = 1;
448  encoderParameters.Parameter[0].Guid = Gdiplus::EncoderQuality;
449  encoderParameters.Parameter[0].Type = Gdiplus::EncoderParameterValueTypeLong;
450  encoderParameters.Parameter[0].NumberOfValues = 1;
451  ULONG quality = qual;
452  encoderParameters.Parameter[0].Value = &quality;
453  outbm->Save(wcstring.data(), &jpegClsid, &encoderParameters);
454  delete outbm;
455  return std::make_pair(true, String{});
456 }
457 #endif
458 
468 void crn::impl::SaveJPEG(const ImageGray &img, const Path &fname, unsigned int qual)
469 {
470  if (!fname)
471  throw ExceptionInvalidArgument(StringUTF8("bool crn::SaveJPEG(const ImageGray &img, const Path &fname, int qual): ") +
472  _("Null file name."));
473 
474  std::lock_guard<std::mutex> lock(crn::FileShield::GetMutex(fname)); // lock the file
475 
476  std::pair<bool, String> res(std::make_pair(false, String(U"")));
477  String error;
478 #ifdef CRN_USING_LIBJPEG
479  if (res.first == false)
480  {
481  res = save_jpeg_libjpeg(fname, img, qual);
482  error += U" " + res.second;
483  }
484 #endif
485 #ifdef CRN_USING_GDIPLUS
486  if (res.first == false)
487  {
488  res = save_jpeg_gdiplus(fname, img, qual);
489  error += U" " + res.second;
490  }
491 #endif
492 #ifdef CRN_USING_GDKPB
493  if (res.first == false)
494  {
495  res = save_jpeg_gdkpixbuf(fname, img, qual);
496  error += U" " + res.second;
497  }
498 #endif
499  if (res.first == false)
500  throw ExceptionRuntime(StringUTF8("void crn::SaveJPEG(const ImageGray &img, const Path &fname, int qual): ") +
501  _("No library for saving image found or write permissions on the file or directory are not granted. No image will be saved.") + StringUTF8(error));
502 }
503 
507 void crn::Sqrt(ImageDoubleGray &img) noexcept
508 {
509  for (auto &px : img)
510  px = sqrt(px);
511 }
512 
517 {
518  int pb = 0, pw = 255;
519  int ppb = 0, ppw = 255;
520  auto h = MakeHistogram(img);
521  do
522  {
523  unsigned int cumb = 0, cumw = 0;
524  size_t nbb = 0, nbw = 0;
525  for (int val = 0; val < int(h.Size()); ++val)
526  {
527  int db = Abs(pb - val);
528  int dw = Abs(pw - val);
529  if (db < dw)
530  {
531  cumb += val * h.GetBin(val);
532  nbb += h.GetBin(val);
533  }
534  else
535  {
536  cumw += val * h.GetBin(val);
537  nbw += h.GetBin(val);
538  }
539  }
540  ppb = pb;
541  ppw = pw;
542  if (nbb)
543  pb = cumb / int(nbb);
544  if (nbw)
545  pw = cumw / int(nbw);
546  } while ((ppb != pb) || (ppw != pw));
547  int diff = pw - pb;
548  if (diff != 255)
549  {
550  for (auto tmp : Range(img))
551  {
552  if (img.At(tmp) < pb)
553  img.At(tmp) = 0;
554  else if (img.At(tmp) > pw)
555  img.At(tmp) = 255;
556  else
557  {
558  img.At(tmp) = uint8_t(((img.At(tmp) - pb) * 255) / diff);
559  }
560  }
561  }
562 }
563 
568 {
569  auto h = Histogram(img.GetHeight());
570  for (size_t y = 0; y < img.GetHeight(); y++)
571  {
572  unsigned int cnt = 0;
573  for (size_t x = 0; x < img.GetWidth(); x++)
574  cnt += img.At(x, y);
575  h.SetBin(y, cnt / 255);
576  }
577  return h;
578 }
579 
584 {
585  auto h = Histogram(img.GetWidth());
586  for (size_t x = 0; x < img.GetWidth(); x++)
587  {
588  unsigned int cnt = 0;
589  for (size_t y = 0; y < img.GetHeight(); y++)
590  cnt += img.At(x, y);
591  h.SetBin(x, cnt / 255);
592  }
593  return h;
594 }
595 
596 
602 {
603  auto h = Histogram(256);
604  for (auto v : img)
605  h.IncBin(v);
606  return h;
607 }
608 
609 /*****************************************************************************/
616 size_t crn::EstimateLinesXHeight(const ImageGray &img, unsigned int xdiv)
617 {
618  auto ig = ImageGray(img.GetWidth() / xdiv, img.GetHeight());
619  FOREACHPIXEL(x, y, ig)
620  {
621  uint8_t minv = 255;
622  for (size_t tx = x * xdiv; tx < Min(img.GetWidth(), (x + 1) * xdiv); ++tx)
623  {
624  if (img.At(tx, y) < minv)
625  minv = img.At(tx, y);
626  }
627  ig.At(x, y) = minv;
628  }
629  auto ibw = Fisher(ig);
630  ig = ImageGray{}; // free memory
631  auto m1 = MatrixInt(1, 3, 1);
632  ibw.Dilate(m1);
633  auto m2 = MatrixInt(1, 5, 1);
634  ibw.Erode(m2);
635  auto h = Histogram(250);
636  for (size_t x = 0; x < ibw.GetWidth(); ++x)
637  {
638  size_t y = 0;
639  bool stop = false;
640  do
641  {
642  while (ibw.At(x, y))
643  {
644  y += 1;
645  if (y >= ibw.GetHeight())
646  {
647  stop = true;
648  break;
649  }
650  }
651  if (stop)
652  break;
653  auto by = y;
654  while (!ibw.At(x, y))
655  {
656  y += 1;
657  if (y >= ibw.GetHeight())
658  {
659  stop = true;
660  break;
661  }
662  }
663  h.IncBin(Min(size_t(249), y - by));
664  } while (!stop);
665  }
666  h.AverageSmoothing(2);
667  return int(Max(size_t(floor(h.Mean())), h.Argmax()));
668 }
669 
673 size_t crn::EstimateLeading(const ImageGray &img)
674 {
675  const auto sw = StrokesWidth(img);
676 
677  auto ly = ImageIntGray(img.GetWidth(), img.GetHeight());
678  for (auto tmp : Range(ly))
679  ly.At(tmp) = img.At(tmp);
680  ly.Dilate(crn::MatrixInt{2 * sw + 1, 1, 1});
681  ly.Dilate(crn::MatrixInt{1, 6 * sw + 1, 1});
682  ly.Erode(crn::MatrixInt{1, 16 * sw + 1, 1});
683  ly.Convolve(crn::MatrixDouble::NewGaussianLine(double(sw)));
684  auto gy = crn::MatrixDouble::NewGaussianLineDerivative(double(sw)/3.0);
685  gy.Transpose();
686  ly.Convolve(gy);
687  for (auto tmp : Range(ly))
688  if (ly.At(tmp) < 0)
689  ly.At(tmp) *= -1;
690  else
691  ly.At(tmp) = 0;
692 
693  auto bl = Fisher(ly);
694  auto histo = crn::Histogram{img.GetHeight()};
695  for (size_t x = 0; x < img.GetWidth(); ++x)
696  {
697  bool w = bl.At(x, 0);
698  size_t by = 0;
699  for (size_t y = 1; y < img.GetHeight(); ++y)
700  {
701  if (bl.At(x, y))
702  {
703  if (!w)
704  {
705  w = true;
706  }
707  }
708  else
709  {
710  if (w)
711  {
712  histo.IncBin(y - by);
713  by = y;
714  w = false;
715  }
716  }
717  }
718  }
719  return histo.MedianValue();
720 }
721 
728 {
729  size_t xh = EstimateLinesXHeight(img);
730  size_t div = xh / 2;
731  if (div == 0)
732  return 0;
733  // create a thumbnail
734  auto ig = ImageGray(img.GetWidth() / div, img.GetHeight());
735  FOREACHPIXEL(x, y, ig)
736  {
737  uint8_t minv = 255;
738  for (size_t tx = x * div; tx < Min(img.GetWidth(), (x + 1) * div); ++tx)
739  {
740  if (img.At(tx, y) < minv)
741  minv = img.At(tx, y);
742  }
743  ig.At(x, y) = minv;
744  }
745  // binarize and filter
746  auto ibw = std::make_shared<ImageBW>(Fisher(ig));
747  ig = ImageGray{}; // free memory
748  auto m = MatrixInt(1, 3, 1);
749  ibw->Dilate(m);
750  auto m2 = MatrixInt(1, 9, 1);
751  ibw->Erode(m2);
752  // extract lines
753  auto lb = Block::New(ibw);
754  auto mask = lb->ExtractCC(U"cc");
755  lb->FilterMinOr(U"cc", 3, 3);
756  long U = 0, V = 0;
757  for (auto cco : lb->GetTree(U"cc"))
758  {
759  auto cc = std::static_pointer_cast<Block>(cco);
760  auto bbox = cc->GetAbsoluteBBox();
761  auto val = cc->GetName().ToInt();
762  auto maxval = int(Min(bbox.GetWidth(), bbox.GetHeight()));
763  Point2DInt topleft, topright, bottomleft, bottomright;
764  bool tl = false, tr = false, bl = false, br = false;
765  for (int tmp = 1; tmp < maxval; ++tmp)
766  {
767  for (int off = 0; off <= tmp; ++off)
768  {
769  if (!tl)
770  if (mask->At(bbox.GetLeft() + off, bbox.GetTop() + tmp - off) == val)
771  {
772  topleft.X = bbox.GetLeft() + off;
773  topleft.Y = bbox.GetTop() + tmp - off;
774  tl = true;
775  }
776  if (!tr)
777  if (mask->At(bbox.GetRight() - off, bbox.GetTop() + tmp - off) == val)
778  {
779  topright.X = bbox.GetRight() - off;
780  topright.Y = bbox.GetTop() + tmp - off;
781  tr = true;
782  }
783  if (!bl)
784  if (mask->At(bbox.GetLeft() + off, bbox.GetBottom() - tmp + off) == val)
785  {
786  bottomleft.X = bbox.GetLeft() + off;
787  bottomleft.Y = bbox.GetBottom() - tmp + off;
788  bl = true;
789  }
790  if (!br)
791  if (mask->At(bbox.GetRight() - off, bbox.GetBottom() - tmp + off) == val)
792  {
793  bottomright.X = bbox.GetRight() - off;
794  bottomright.Y = bbox.GetBottom() - tmp + off;
795  br = true;
796  }
797  }
798  if (tl && tr && bl && br)
799  break;
800  }
801  if (tr && tl)
802  {
803  U += long(div) * (topright.X - topleft.X);
804  V += topright.Y - topleft.Y;
805  }
806  if (br && bl)
807  {
808  U += long(div) * (bottomright.X - bottomleft.X);
809  V += bottomright.Y - bottomleft.Y;
810  }
811  }
812  return Angle<Radian>::Atan(double(-V), double(U)); // Y axis is inverted
813 }
814 
819 {
820  UserData.Set(U"threshold", std::make_shared<Int>(t));
821 }
822 
824 {
825  return Threshold(img, uint8_t(*std::static_pointer_cast<Int>(UserData[U"threshold"])));
826 }
827 
832 Gray2BWNiblack::Gray2BWNiblack(size_t halfwin, double k)
833 {
834  UserData.Set(U"halfwin", std::make_shared<Int>(int(halfwin)));
835  UserData.Set(U"k", std::make_shared<Real>(k));
836 }
837 
839 {
840  return Niblack(img, *std::static_pointer_cast<Int>(UserData[U"halfwin"]),
841  *std::static_pointer_cast<Real>(UserData[U"k"]));
842 }
843 
848 Gray2BWSauvola::Gray2BWSauvola(size_t halfwin, double k)
849 {
850  UserData.Set(U"halfwin", std::make_shared<Int>(int(halfwin)));
851  UserData.Set(U"k", std::make_shared<Real>(k));
852 }
853 
855 {
856  return Sauvola(img, *std::static_pointer_cast<Int>(UserData[U"halfwin"]),
857  *std::static_pointer_cast<Real>(UserData[U"k"]));
858 }
859 
864 Gray2BWkMeansHisto::Gray2BWkMeansHisto(size_t classes, size_t black_classes)
865 {
866  UserData.Set(U"classes", std::make_shared<Int>(int(classes)));
867  UserData.Set(U"black_classes", std::make_shared<Int>(int(black_classes)));
868 }
869 
871 {
872  return kMeansHisto(img, *std::static_pointer_cast<Int>(UserData[U"classes"]),
873  *std::static_pointer_cast<Int>(UserData[U"black_classes"]));
874 }
875 
880 {
881  UserData.Set(U"area", std::make_shared<Int>(int(area)));
882 }
883 
885 {
886  return LocalMin(img, *std::static_pointer_cast<Int>(UserData[U"area"]));
887 }
888 
893 {
894  UserData.Set(U"area", std::make_shared<Int>(int(area)));
895 }
896 
898 {
899  return LocalMax(img, *std::static_pointer_cast<Int>(UserData[U"area"]));
900 }
901 
905 {
906 }
907 
909 {
910  return Fisher(img);
911 }
912 
916 {
917 }
918 
920 {
921  return Entropy(img);
922 }
923 
927 {
928 }
929 
931 {
932  return Otsu(img);
933 }
934 
936 {
937  auto act = std::dynamic_pointer_cast<Gray2BW>(DefaultAction::GetAction(U"Gray2BW"));
938  if (!act)
939  {
940  act = std::make_shared<Gray2BWFisher>();
941  DefaultAction::SetAction(U"Gray2BW", act);
942  }
943  return act->Binarize(img);
944 }
945 
947  CRN_DATA_FACTORY_REGISTER(U"Gray2BWThreshold", Gray2BWThreshold)
948 CRN_END_CLASS_CONSTRUCTOR(Gray2BWThreshold)
949 
951  CRN_DATA_FACTORY_REGISTER(U"Gray2BWNiblack", Gray2BWNiblack)
952 CRN_END_CLASS_CONSTRUCTOR(Gray2BWNiblack)
953 
955  CRN_DATA_FACTORY_REGISTER(U"Gray2BWSauvola", Gray2BWSauvola)
956 CRN_END_CLASS_CONSTRUCTOR(Gray2BWSauvola)
957 
959  CRN_DATA_FACTORY_REGISTER(U"Gray2BWkMeansHisto", Gray2BWkMeansHisto)
960 CRN_END_CLASS_CONSTRUCTOR(Gray2BWkMeansHisto)
961 
963  CRN_DATA_FACTORY_REGISTER(U"Gray2BWLocalMin", Gray2BWLocalMin)
964 CRN_END_CLASS_CONSTRUCTOR(Gray2BWLocalMin)
965 
967  CRN_DATA_FACTORY_REGISTER(U"Gray2BWLocalMax", Gray2BWLocalMax)
968 CRN_END_CLASS_CONSTRUCTOR(Gray2BWLocalMax)
969 
971  CRN_DATA_FACTORY_REGISTER(U"Gray2BWFisher", Gray2BWFisher)
972 CRN_END_CLASS_CONSTRUCTOR(Gray2BWFisher)
973 
975  CRN_DATA_FACTORY_REGISTER(U"Gray2BWEntropy", Gray2BWEntropy)
976 CRN_END_CLASS_CONSTRUCTOR(Gray2BWEntropy)
977 
979  CRN_DATA_FACTORY_REGISTER(U"Gray2BWOtsu", Gray2BWOtsu)
980 CRN_END_CLASS_CONSTRUCTOR(Gray2BWOtsu)
981 
static MatrixDouble NewGaussianLineDerivative(double sigma)
Creates a line matrix with the derivative of a centered Gaussian.
void AutoContrast(ImageGray &img)
ImageBW Sauvola(const Image< T > &img, size_t halfwin, double k=0.5, typename std::enable_if< std::is_arithmetic< T >::value >::type *dummy=nullptr)
Definition: CRNImageGray.h:282
ImageBW Entropy(const Image< T > &img, typename std::enable_if< std::is_arithmetic< T >::value >::type *dummy=nullptr)
Definition: CRNImageGray.h:553
virtual ImageBW Binarize(const ImageGray &img) override
Action = binarize a gray image.
ScalarRange< T > Range(T b, T e)
Creates a range [[b, e[[.
Definition: CRNType.h:257
ImageBW Fisher(const Image< T > &img, typename std::enable_if< std::is_arithmetic< T >::value >::type *dummy=nullptr)
Definition: CRNImageGray.h:521
ImageBW MakeImageBW(const ImageGray &img)
Gray2BWNiblack(size_t halfwin=3, double k=0.5)
Default constructor.
void fclose_if_not_null(FILE *f)
Definition: CRNImage.cpp:68
A generic runtime error.
Definition: CRNException.h:131
Image< int > ImageIntGray
Int grayscale image class.
Fisher binarization action.
Definition: CRNImageGray.h:732
virtual ImageBW Binarize(const ImageGray &img) override
Action = binarize a gray image.
std::vector< pixel_type >::reference At(size_t x, size_t y) noexcept
Returns a reference to a pixel.
Definition: CRNImage.h:224
size_t GetHeight() const noexcept
Definition: CRNImage.h:74
#define _(String)
Definition: CRNi18n.h:51
Image< uint8_t > ImageGray
Grayscale image class.
static MatrixDouble NewGaussianLine(double sigma)
Creates a line matrix with a centered Gaussian.
const T & Max(const T &a, const T &b)
Returns the max of two values.
Definition: CRNMath.h:47
virtual ImageBW Binarize(const ImageGray &img) override
Action = binarize a gray image.
Binarization action.
Definition: CRNImageGray.h:631
void Set(const String &key, SObject value)
Sets a value for a key with constraints check.
Definition: CRNMap.cpp:92
Gray2BWFisher()
Default constructor.
Niblack binarization action.
Definition: CRNImageGray.h:657
void SavePNG(const ImageBW &img, const Path &fname)
Saves as PNG file.
Otsu binarization action.
Definition: CRNImageGray.h:762
Gray2BWkMeansHisto(size_t classes=5, size_t black_classes=3)
Default constructor.
ImageBW kMeansHisto(const Image< T > &img, size_t classes, size_t black_classes, size_t maxcnt=10000, typename std::enable_if< std::is_arithmetic< T >::value >::type *dummy=nullptr)
Definition: CRNImageGray.h:335
#define CRN_END_CLASS_CONSTRUCTOR(classname)
Defines a class constructor.
Definition: CRNObject.h:198
#define FOREACHPIXEL(x, y, img)
Convenience macro to sweep an image.
Definition: CRNImage.h:37
A convenience class for angles units.
void Sqrt(ImageDoubleGray &img) noexcept
Replaces the pixels with their square root.
static std::mutex & GetMutex(const Path &fname)
Gets the mutex associated to a file.
A UTF32 character string class.
Definition: CRNString.h:61
Integer matrix class.
Definition: CRNMatrixInt.h:40
ImageBW LocalMin(const Image< T > &img, size_t area=1, typename std::enable_if< std::is_arithmetic< T >::value >::type *dummy=nullptr)
Definition: CRNImageGray.h:404
virtual ImageBW Binarize(const ImageGray &img) override
Action = binarize a gray image.
size_t EstimateLeading(const ImageGray &img)
Computes the median distance between two baselines.
ImageBW Niblack(const Image< T > &img, size_t halfwin, double k=0.5, typename std::enable_if< std::is_arithmetic< T >::value >::type *dummy=nullptr)
Definition: CRNImageGray.h:229
static SBlock New(const SImage &src, const String &nam=U"")
Top block creator.
Definition: CRNBlock.cpp:46
A block.
Definition: CRNBlock.h:52
static SAction GetAction(const String &name)
Gets a default action.
const char * CStr() const noexcept
Conversion to UTF8 cstring.
const Rect & GetAbsoluteBBox() const noexcept
Gets the absolute bounding box of the block.
Definition: CRNBlock.h:71
A convenience class for file paths.
Definition: CRNPath.h:39
value_type X
Definition: CRNPoint2D.h:63
#define CRN_DATA_FACTORY_REGISTER(elemname, classname)
Registers a class to the data factory.
ImageBW Otsu(const Image< T > &img, typename std::enable_if< std::is_arithmetic< T >::value >::type *dummy=nullptr)
Definition: CRNImageGray.h:585
Threshold binarization action.
Definition: CRNImageGray.h:642
virtual ImageBW Binarize(const ImageGray &img) override
Action = binarize a gray image.
Gray2BWOtsu()
Default constructor.
ImageBW Threshold(const Image< T > &img, T thresh, CMP cmp=std::less< T >{})
Definition: CRNImageGray.h:204
Gray2BWLocalMax(size_t area=1)
Default constructor.
void Abs(Image< T > &img, typename std::enable_if< std::is_arithmetic< T >::value >::type *dummy=nullptr) noexcept
Replaces each pixel by its absolute value.
Definition: CRNImageGray.h:47
virtual ImageBW Binarize(const ImageGray &img) override
Action = binarize a gray image.
const T & Min(const T &a, const T &b)
Returns the min of two values.
Definition: CRNMath.h:49
size_t EstimateLinesXHeight(const ImageGray &img, unsigned int xdiv=16)
Computes the mean text line x-height.
Mother class for integer histograms.
Definition: CRNHistogram.h:44
value_type Y
Definition: CRNPoint2D.h:63
Entropy binarization action.
Definition: CRNImageGray.h:747
Local max binarization action.
Definition: CRNImageGray.h:717
Gray2BWLocalMin(size_t area=1)
Default constructor.
ImageBW LocalMax(const Image< T > &img, size_t area=1, typename std::enable_if< std::is_arithmetic< T >::value >::type *dummy=nullptr)
Definition: CRNImageGray.h:463
void SaveJPEG(const ImageBW &img, const Path &fname, unsigned int qual)
Saves as JPEG file.
size_t GetWidth() const noexcept
Definition: CRNImage.h:72
Path & ToLocal()
Converts the path to the local format.
Definition: CRNPath.cpp:534
Angle< Radian > EstimateSkew(const ImageGray &img)
Estimates the mean skew of the document's lines.
size_t StrokesWidth(const Image< T > &img, size_t maxval=50, size_t defaultval=0, typename std::enable_if< std::is_arithmetic< T >::value >::type *dummy=nullptr)
Definition: CRNImageGray.h:90
Histogram HorizontalProjection(const ImageBW &img)
Computes the horizontal projection.
Definition: CRNImageBW.cpp:574
A character string class.
Definition: CRNStringUTF8.h:49
Sauvola binarization action.
Definition: CRNImageGray.h:672
crn::Map UserData
k-means histo binarization action
Definition: CRNImageGray.h:687
A 2D point class.
Definition: CRNPoint2DInt.h:39
virtual ImageBW Binarize(const ImageGray &img) override
Action = binarize a gray image.
Histogram MakeHistogram(const Image< T > &img, typename std::enable_if< std::is_arithmetic< T >::value >::type *dummy=nullptr)
Definition: CRNImageGray.h:64
#define TRUE
Definition: CRNProp3.cpp:28
Gray2BWSauvola(size_t halfwin=3, double k=0.5)
Default constructor.
virtual ImageBW Binarize(const ImageGray &img) override
Action = binarize a gray image.
virtual ImageBW Binarize(const ImageGray &img) override
Action = binarize a gray image.
Histogram VerticalProjection(const ImageBW &img)
Computes the vertical projection.
Definition: CRNImageBW.cpp:591
static void SetAction(const String &name, SAction action)
Sets a default action.
Invalid argument error (e.g.: nullptr pointer)
Definition: CRNException.h:107
#define CRN_BEGIN_CLASS_CONSTRUCTOR(classname)
Defines a class constructor.
Definition: CRNObject.h:185
Local min binarization action.
Definition: CRNImageGray.h:702
Gray2BWThreshold(uint8_t t=127)
Default constructor.
Gray2BWEntropy()
Default constructor.
static Angle Atan(double tangent) noexcept(std::is_nothrow_constructible< typename Unit::type >::value)
Computes arc tangent.