Recent

Author Topic: Image2text  (Read 12215 times)

lainz

  • Hero Member
  • *****
  • Posts: 4460
    • https://lainz.github.io/
Image2text
« on: May 11, 2016, 03:50:11 am »
Hi, this is my simple image to text converter!

Here is the full source code (attached project for those who want to download instead):
Code: Pascal  [Select][+][-]
  1. unit Unit1;
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. interface
  6.  
  7. uses
  8.   Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, StdCtrls,
  9.   BCButton, BGRABitmap, BGRABitmapTypes;
  10.  
  11. type
  12.  
  13.   { TForm1 }
  14.  
  15.   TForm1 = class(TForm)
  16.     BCButton1: TBCButton;
  17.     Memo1: TMemo;
  18.     OpenDialog1: TOpenDialog;
  19.     procedure BCButton1Click(Sender: TObject);
  20.   private
  21.     { private declarations }
  22.     TheImage: string;
  23.     function Convertir: string;
  24.   public
  25.     { public declarations }
  26.   end;
  27.  
  28. var
  29.   Form1: TForm1;
  30.  
  31. implementation
  32.  
  33. {$R *.lfm}
  34.  
  35. { TForm1 }
  36.  
  37. procedure TForm1.BCButton1Click(Sender: TObject);
  38. begin
  39.   if OpenDialog1.Execute then
  40.   begin
  41.     TheImage := OpenDialog1.FileName;
  42.     Memo1.Text:=Convertir;
  43.   end;
  44. end;
  45.  
  46. function TForm1.Convertir: string;
  47. var
  48.   x, y: integer;
  49.   p: PBGRAPixel;
  50.   output, pix: string;
  51.   Bitmap: TBGRABitmap;
  52. begin
  53.   Bitmap := TBGRABitmap.Create(TheImage);
  54.  
  55.   BGRAReplace(Bitmap, Bitmap.Resample(100,50,rmSimpleStretch));
  56.  
  57.   Bitmap.InplaceGrayscale;
  58.  
  59.   for y := 0 to Bitmap.Height - 1 do
  60.   begin
  61.     p := Bitmap.Scanline[y];
  62.     for x := 0 to Bitmap.Width - 1 do
  63.     begin
  64.  
  65.       case p^.blue of
  66.         0..63: pix := '█';
  67.         64..127: pix := '▓';
  68.         128..191: pix := '▒';
  69.         192..254: pix := '░';
  70.         255: pix := ' ';
  71.       end;
  72.  
  73.       output := output + pix;
  74.  
  75.       if x = Bitmap.Width-1 then
  76.         output := output + LineEnding;
  77.  
  78.       Inc(p);
  79.     end;
  80.   end;
  81.  
  82.   Bitmap.InvalidateBitmap;
  83.  
  84.   Bitmap.Free;
  85.  
  86.   result := output;
  87. end;
  88.  
  89. end.
  90.  
  91.  

engkin

  • Hero Member
  • *****
  • Posts: 3112
Re: Image2text
« Reply #1 on: May 11, 2016, 04:26:30 am »
You tricked me, I thought you meant an OCR.  :P

aradeonas

  • Hero Member
  • *****
  • Posts: 824
Re: Image2text
« Reply #2 on: May 11, 2016, 09:24:07 am »
Cool! I liked it ;)

aradeonas

  • Hero Member
  • *****
  • Posts: 824
Re: Image2text
« Reply #3 on: May 11, 2016, 09:37:26 am »
Here is a version with better light and alpha support I think :
Quote
      h:=BGRAToHSLA(p^);
      v:=round((h.lightness/65535)*100);

      case v of
        0..25: pix := '█';
        26..50: pix := '▓';
        51..75: pix := '▒';
        76..99: pix := '░';
        100: pix := ' ';
      end;
      if h.alpha=0 then
     pix := ' ';

lainz

  • Hero Member
  • *****
  • Posts: 4460
    • https://lainz.github.io/
Re: Image2text
« Reply #4 on: May 11, 2016, 04:25:20 pm »
You tricked me, I thought you meant an OCR.  :P

LOL

Cool! I liked it ;)

Thanks

Here is a version with better light and alpha support I think :
Quote
      h:=BGRAToHSLA(p^);
      v:=round((h.lightness/65535)*100);

      case v of
        0..25: pix := '█';
        26..50: pix := '▓';
        51..75: pix := '▒';
        76..99: pix := '░';
        100: pix := ' ';
      end;
      if h.alpha=0 then
     pix := ' ';

Yes, in fact also you can add a lot of more characters to it in the ranges you want.

Also I need to change the resample width and height for ones that follow the proportion of the picture. Like width 100 and height according to original widht and height proportion.

lainz

  • Hero Member
  • *****
  • Posts: 4460
    • https://lainz.github.io/
Re: Image2text
« Reply #5 on: June 24, 2016, 10:47:29 pm »
Hi, this is an *improved* version that should run faster because the string size is initialized. However I can't figure how to do it with the same characters used before because these don't fit in char, are unicode characters, any idea in how to calcule the appropriate size and in general the whole function with unicode characters ( I know their size can vary from one to several bytes, but no more than that ) ... ?

Code: Pascal  [Select][+][-]
  1. function TForm1.Convertir: string;
  2. var
  3.   x, y, i: integer;
  4.   p: PBGRAPixel;
  5.   pix: char;
  6.   output: string;
  7.   Bitmap: TBGRABitmap;
  8. begin
  9.   Bitmap := TBGRABitmap.Create(TheImage);
  10.  
  11.   BGRAReplace(Bitmap, Bitmap.Resample(100,50,rmSimpleStretch));
  12.  
  13.   // width + 2 (lineEnding) * height
  14.   SetLength(output, 102 * 50);
  15.  
  16.   Bitmap.InplaceGrayscale;
  17.  
  18.   i := 1;
  19.  
  20.   for y := 0 to Bitmap.Height - 1 do
  21.   begin
  22.     p := Bitmap.Scanline[y];
  23.     for x := 0 to Bitmap.Width - 1 do
  24.     begin
  25.  
  26.       case p^.blue of
  27.         0..63: pix := '0';
  28.         64..127: pix := '1';
  29.         128..191: pix := '2';
  30.         192..254: pix := '3';
  31.         255: pix := ' ';
  32.       end;
  33.  
  34.       output[i] := pix;
  35.  
  36.       if x = Bitmap.Width-1 then
  37.       begin
  38.         output[i+1] := #13;
  39.         output[i+2] := #10;
  40.         Inc(i, 2);
  41.       end;
  42.  
  43.       Inc(p);
  44.       Inc(i);
  45.     end;
  46.   end;
  47.  
  48.   Bitmap.InvalidateBitmap;
  49.  
  50.   Bitmap.Free;
  51.  
  52.   result := output;
  53. end;

circular

  • Hero Member
  • *****
  • Posts: 4195
    • Personal webpage
Re: Image2text
« Reply #6 on: June 24, 2016, 10:57:50 pm »
You can use a PChar. Allocate a memory buffer with the maximum size, for example supposing 4 bytes per characters and 1 null character to finish the string. Then copy them using a PChar containing the current address and increment. Finally add the null char and convert to string.
Conscience is the debugger of the mind

lainz

  • Hero Member
  • *****
  • Posts: 4460
    • https://lainz.github.io/
Re: Image2text
« Reply #7 on: June 25, 2016, 12:15:16 am »
This is the way I solved it, maybe is not as good as using Pchar but i'm not sure how to use Pchar, I have a lot to learn :)

The size of each character is 3, except for whitespace that is 1, LineEnding is 2: #13 and #10. So we can calculate an aproximated value, can be only less if we use whitespace.

I need to fill it with whitespace in order to don't get garbage (If I fill it with #0 does not works). Then I use move to copy the character to the desired position. Finally I remove all the remaining whitespace filling with #0.

Code: Pascal  [Select][+][-]
  1. function TForm1.Convertir: string;
  2. var
  3.   x, y, i: integer;
  4.   p: PBGRAPixel;
  5.   pix: string;
  6.   output: string;
  7.   Bitmap: TBGRABitmap;
  8. begin
  9.   Bitmap := TBGRABitmap.Create(TheImage);
  10.  
  11.   BGRAReplace(Bitmap, Bitmap.Resample(100, 50, rmSimpleStretch));
  12.  
  13.   // width * 3 (char lenght) + 2 (lineEnding) * height
  14.   SetLength(output, (302) * 50);
  15.  
  16.   // fill with whitespace to prevent garbage
  17.   FillChar(output[1], Length(output), ' ');
  18.  
  19.   Bitmap.InplaceGrayscale;
  20.  
  21.   i := 1;
  22.  
  23.   for y := 0 to Bitmap.Height - 1 do
  24.   begin
  25.     p := Bitmap.Scanline[y];
  26.     for x := 0 to Bitmap.Width - 1 do
  27.     begin
  28.  
  29.       case p^.red of
  30.         0..63: pix := '█';
  31.         64..127: pix := '▓';
  32.         128..191: pix := '▒';
  33.         192..254: pix := '░';
  34.         255: pix := ' ';
  35.       end;
  36.  
  37.       Move(pix[1], output[i], Length(pix));
  38.  
  39.       Inc(i, Length(pix));
  40.  
  41.       if x = Bitmap.Width - 1 then
  42.       begin
  43.         output[i + 1] := #13;
  44.         output[i + 2] := #10;
  45.         Inc(i, 2);
  46.       end;
  47.  
  48.       Inc(p);
  49.     end;
  50.   end;
  51.  
  52.   // Fill whitespace with #0
  53.   FillChar(output[i], Length(output) - i, #0);
  54.  
  55.   Bitmap.InvalidateBitmap;
  56.  
  57.   Bitmap.Free;
  58.  
  59.   Result := output;
  60. end;

molly

  • Hero Member
  • *****
  • Posts: 2330
Re: Image2text
« Reply #8 on: June 25, 2016, 12:20:03 am »
May i suggest using a 2-dimensional array of chars (whatever char type you prefer) ?

That does mean you would have to run the same loop at the end to stuff it into a string (if that is what you prefer), but at least you would not have to worry about whatever conversion as the compiler should take care of it. Could be you are really trying to address the speed issue, but in that case i would not attempt to let the result contain whatever unicode chars whatsoever.

Oh and btw: please never ever assume LineEnding is always #13#10, let alone that it always consumes 2 bytes to indicate it is a line-ending (because it isn't for all platforms).

edit: see here for the lineending declaration, and the wiki mentions some things about it as well.
« Last Edit: June 25, 2016, 12:40:22 am by molly »

lainz

  • Hero Member
  • *****
  • Posts: 4460
    • https://lainz.github.io/
Re: Image2text
« Reply #9 on: June 25, 2016, 12:56:04 am »
It should be a char type that supports at least 3 bytes, at least for those characters I selected.

I know, LineEnding is different in each OS, I think I was testing it just with chars and never came up it will work in first time :) Sure it can be changed easily getting the lenght of LineEnding const and that's all. I must change the

output[i + 1] := #13;
output[i + 2] := #10;

with the corresponding Move(...) to fill always the correct one, and in the size calculation of course.

About using unicode characters, tested under windows and it works fine as is.

Edit: Here it is again. Thanks for your help (both)

Code: Pascal  [Select][+][-]
  1. function TForm1.Convertir: string;
  2. var
  3.   x, y, i: integer;
  4.   p: PBGRAPixel;
  5.   pix: string;
  6.   output: string;
  7.   Bitmap: TBGRABitmap;
  8. begin
  9.   Bitmap := TBGRABitmap.Create(TheImage);
  10.  
  11.   BGRAReplace(Bitmap, Bitmap.Resample(100, 50, rmSimpleStretch));
  12.  
  13.   // width * 3 (char length) + (lineEnding) * height
  14.   SetLength(output, (300 + Length(LineEnding)) * 50);
  15.  
  16.   // fill with whitespace to prevent garbage
  17.   FillChar(output[1], Length(output), ' ');
  18.  
  19.   Bitmap.InplaceGrayscale;
  20.  
  21.   i := 1;
  22.  
  23.   for y := 0 to Bitmap.Height - 1 do
  24.   begin
  25.     p := Bitmap.Scanline[y];
  26.     for x := 0 to Bitmap.Width - 1 do
  27.     begin
  28.  
  29.       case p^.red of
  30.         0..63: pix := '█';
  31.         64..127: pix := '▓';
  32.         128..191: pix := '▒';
  33.         192..254: pix := '░';
  34.         255: pix := ' ';
  35.       end;
  36.  
  37.       Move(pix[1], output[i], Length(pix));
  38.       Inc(i, Length(pix));
  39.  
  40.       if x = Bitmap.Width - 1 then
  41.       begin
  42.         Move(LineEnding[1], output[i], Length(LineEnding));
  43.         Inc(i, Length(LineEnding));
  44.       end;
  45.  
  46.       Inc(p);
  47.     end;
  48.   end;
  49.  
  50.   // Fill whitespace with #0
  51.   FillChar(output[i], Length(output) - i, #0);
  52.  
  53.   Bitmap.InvalidateBitmap;
  54.  
  55.   Bitmap.Free;
  56.  
  57.   Result := output;
  58. end;      
« Last Edit: June 25, 2016, 01:00:18 am by lainz »

molly

  • Hero Member
  • *****
  • Posts: 2330
Re: Image2text
« Reply #10 on: June 25, 2016, 01:30:22 am »
With regards to the lineending:
Ok no problemo. I thought perhaps you did not know, hence my comment.

With regards to your code, please feel free to compare with the following (following your lead):
Code: Pascal  [Select][+][-]
  1. procedure TForm1.Button6Click(Sender: TObject);
  2. const
  3.   newwidth   = 100;
  4.   newheight  = 50;
  5. var
  6.   x, y, i: integer;
  7.   p      : PBGRAPixel;
  8.   pix    : String;
  9.   OutPut : array[0..Pred(newheight), 0..Pred(newwidth)] of String;
  10.   Bitmap: TBGRABitmap;
  11.   S : String;
  12. begin
  13.   Bitmap := TBGRABitmap.Create(TheBitmapFilename);
  14.  
  15.   BGRAReplace(Bitmap, Bitmap.Resample(100, 50, rmSimpleStretch));
  16.  
  17.   Bitmap.InplaceGrayscale;
  18.  
  19.   i := 1;
  20.  
  21.   for y := 0 to Bitmap.Height - 1 do
  22.   begin
  23.     p := Bitmap.Scanline[y];
  24.     for x := 0 to Bitmap.Width - 1 do
  25.     begin
  26.       case p^.red of
  27.         0..63     : pix := '█';
  28.         64..127   : pix := '▓';
  29.         128..191  : pix := '▒';
  30.         192..254  : pix := '░';
  31.         255       : pix := ' ';
  32.       end;
  33.       output[y,x] := pix;
  34.       Inc(p);
  35.     end;
  36.   end;
  37.  
  38.   Bitmap.InvalidateBitmap;
  39.  
  40.   Bitmap.Free;
  41.  
  42.   S := '';
  43.   for y := Low(Output) to High(output) do
  44.   begin
  45.     for x := Low(output[y]) to High(output[y]) do
  46.     begin
  47.       S := S + Output[y,x];
  48.     end;
  49.     S := S + lineEnding;
  50.   end;
  51.  
  52.   Memo1.Text := S;
  53. end;
  54.  

In case you are doing what you do for the experience of it, then i have no comments whatsoever (you simply have to learn things on your own conditions). But i consider the code  as shown by me a teeny bit more readable ?

But, as you can probably see for yourself. why the redundancy ? e.g. did you really improved the performance with your latest code ?
« Last Edit: June 25, 2016, 01:36:35 am by molly »

lainz

  • Hero Member
  • *****
  • Posts: 4460
    • https://lainz.github.io/
Re: Image2text
« Reply #11 on: June 25, 2016, 03:23:07 am »
I measured all, my first code, my latest code and your code and all speed is below 0.0 ms, numbers after comma changes in a way that is difficult to measure wich code is faster. Maybe on bigger strings we can see the difference, is not the case.

If speed is not the difference as you say is better to have readable code.

circular

  • Hero Member
  • *****
  • Posts: 4195
    • Personal webpage
Re: Image2text
« Reply #12 on: June 25, 2016, 01:06:38 pm »
This is the way I solved it, maybe is not as good as using Pchar but i'm not sure how to use Pchar, I have a lot to learn :)
In fact, they way you did by copying chars into the string is better. In the end, instead of filling with zero chars, you need to use SetLength to reduce the size to the actual length.

As molly suggests, it is more portable to use LineEnding constant for the end of lines.

I would also suggest to use a formula for the SetLength like (3*Bitmap.Width + Length(LineEnding)) * Bitmap.Height
Conscience is the debugger of the mind

Bart

  • Hero Member
  • *****
  • Posts: 5275
    • Bart en Mariska's Webstek
Re: Image2text
« Reply #13 on: June 25, 2016, 01:33:26 pm »
Really nice!

I remember seeing a video on YT where some guy converted bitmps to excel spreadsheets, using 3 cells for each pixel (RGB) and using conditional layout, so that the color of each cell depends on the (RGB) value that was in there.

So, using fpspreadsheet, this could be your next project?

Bart

lainz

  • Hero Member
  • *****
  • Posts: 4460
    • https://lainz.github.io/
Re: Image2text
« Reply #14 on: June 25, 2016, 04:14:07 pm »
Thanks all :)

Code: Pascal  [Select][+][-]
  1. function TForm1.Convertir: string;
  2. var
  3.   x, y, i: integer;
  4.   p: PBGRAPixel;
  5.   pix: string;
  6.   output: string;
  7.   Bitmap: TBGRABitmap;
  8. begin
  9.   Bitmap := TBGRABitmap.Create(TheImage);
  10.  
  11.   BGRAReplace(Bitmap, Bitmap.Resample(100, 50, rmSimpleStretch));
  12.  
  13.   // width * 3 (char length) + (lineEnding) * height
  14.   SetLength(output, ((3*Bitmap.Width + Length(LineEnding)) * Bitmap.Height));
  15.  
  16.   // fill with whitespace to prevent garbage
  17.   FillChar(output[1], Length(output), ' ');
  18.  
  19.   Bitmap.InplaceGrayscale;
  20.  
  21.   i := 1;
  22.  
  23.   for y := 0 to Bitmap.Height - 1 do
  24.   begin
  25.     p := Bitmap.Scanline[y];
  26.     for x := 0 to Bitmap.Width - 1 do
  27.     begin
  28.  
  29.       case p^.red of
  30.         0..63: pix := '█';
  31.         64..127: pix := '▓';
  32.         128..191: pix := '▒';
  33.         192..254: pix := '░';
  34.         255: pix := ' ';
  35.       end;
  36.  
  37.       Move(pix[1], output[i], Length(pix));
  38.       Inc(i, Length(pix));
  39.  
  40.       if x = Bitmap.Width - 1 then
  41.       begin
  42.         Move(LineEnding[1], output[i], Length(LineEnding));
  43.         Inc(i, Length(LineEnding));
  44.       end;
  45.  
  46.       Inc(p);
  47.     end;
  48.   end;
  49.  
  50.   SetLength(output, Length(output) - (Length(output) - i + 1));
  51.  
  52.   Bitmap.InvalidateBitmap;
  53.  
  54.   Bitmap.Free;
  55.  
  56.   Result := output;
  57. end;  

 

TinyPortal © 2005-2018